latest.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. import tkinter as tk
  2. class SquareWindow:
  3. def __init__(self, config, path_info=None, path_mapping=None):
  4. # Configuration: [(parent_number, count), ...]
  5. # (None, 11) means draw 11 squares inside square #0 (the main square)
  6. # (2, 7) means draw 7 squares inside square #2
  7. # path_info: Optional dict mapping square numbers to file/folder names
  8. # path_mapping: Optional dict mapping square numbers to file paths
  9. self.config = config
  10. self.path_info = path_info if path_info is not None else {}
  11. self.number_to_path = path_mapping if path_mapping is not None else {}
  12. # Set window size to 1000x1000 pixels (needed for calculate_squares)
  13. self.window_size = 1000
  14. # Calculate squares based on config
  15. self.squares = self.calculate_squares()
  16. # Create the main window
  17. self.root = tk.Tk()
  18. self.root.title("Square Window")
  19. self.root.geometry(f"{self.window_size}x{self.window_size}")
  20. # Create a canvas to draw on
  21. self.canvas = tk.Canvas(self.root, width=self.window_size, height=self.window_size, bg="white")
  22. self.canvas.pack(fill=tk.BOTH, expand=True)
  23. # Property to store square information: {number: (x, y, size, parent_number)}
  24. self.square_positions = {}
  25. # Store original colors for flashing effect
  26. self.original_colors = {}
  27. # Draw all squares based on the calculated squares dictionary
  28. for number, (x, y, size, parent_number) in self.squares.items():
  29. self.draw_square_at_location(x, y, size, number, parent_number)
  30. # Print the square positions dictionary
  31. output_content = f"Square positions: {self.square_positions}"
  32. print(output_content)
  33. # Save the output to print.log file
  34. with open("print.log", "w", encoding="utf-8") as f:
  35. f.write(output_content)
  36. # Initialize clipboard monitoring for file content search
  37. self.highlighted_squares = set()
  38. self.all_highlighted_squares = set() # Track all previously highlighted squares
  39. self.check_clipboard_content()
  40. def calculate_squares(self):
  41. """
  42. Calculate all squares based on the config.
  43. Config format: [(parent_number, count), ...]
  44. (None, count) means draw 'count' squares inside the main square (number 0)
  45. (number, count) means draw 'count' squares inside square with 'number'
  46. """
  47. import math
  48. # Initialize the main square (number 0)
  49. main_square_size = int(self.window_size * 0.9) # 90% of 1000 = 900
  50. margin = (self.window_size - main_square_size) // 2 # (1000 - 900) / 2 = 50
  51. # Dictionary to store all squares: {number: (x, y, size, parent_number)}
  52. squares = {}
  53. # Add the main square (number 0)
  54. squares[0] = (margin, margin, main_square_size, None)
  55. # Track the next available number for new squares
  56. next_number = 1
  57. # Process each configuration entry
  58. for parent_number, count in self.config:
  59. if parent_number is None:
  60. # Draw 'count' squares inside the main square (number 0)
  61. parent_num = 0
  62. else:
  63. parent_num = parent_number
  64. # Get the parent square's info
  65. if parent_num in squares:
  66. parent_x, parent_y, parent_size, _ = squares[parent_num]
  67. # Handle case where count is 0
  68. if count <= 0:
  69. continue # Skip if there are no items to draw
  70. # Calculate grid size for the child squares
  71. n = math.ceil(math.sqrt(count)) # Number of rows/columns needed
  72. child_size = (parent_size // n) - 2 # Size of each child square minus padding
  73. # Calculate spacing to distribute squares evenly with gaps
  74. total_used_space = n * child_size # Total space occupied by squares
  75. remaining_space = parent_size - total_used_space # Remaining space for gaps
  76. gap = remaining_space // (n + 1) # Gap between squares and margins
  77. # Draw 'count' child squares inside the parent square
  78. squares_drawn = 0
  79. for i in range(n): # Rows
  80. for j in range(n): # Columns
  81. if squares_drawn >= count:
  82. break
  83. # Calculate position for each child square with proper spacing
  84. child_x = parent_x + gap + j * (child_size + gap)
  85. child_y = parent_y + gap + i * (child_size + gap)
  86. # Add the child square to the dictionary
  87. squares[next_number] = (child_x, child_y, child_size, parent_num)
  88. next_number += 1
  89. squares_drawn += 1
  90. if squares_drawn >= count:
  91. break
  92. return squares
  93. def draw_square_at_location(self, x, y, size, number, parent_number):
  94. """
  95. Draw a square at a specific location with a number in the center.
  96. Args:
  97. x: X coordinate of the top-left corner
  98. y: Y coordinate of the top-left corner
  99. size: Size of the square (width and height)
  100. number: Number to display in the center
  101. parent_number: The number of the parent square (None if no parent)
  102. """
  103. # Get the name associated with this square number if available
  104. name = self.path_info.get(number, str(number))
  105. # Calculate the level of the square (how deep it is in the hierarchy)
  106. level = self.get_square_level(number)
  107. # Define light colors for different levels (with transparency effect)
  108. colors = [
  109. "#E0E0E0", # Level 0 (light gray, main square)
  110. "#FFCCCC", # Level 1 (light red)
  111. "#CCE5FF", # Level 2 (light blue)
  112. "#CCFFCC", # Level 3 (light green)
  113. "#E5CCFF", # Level 4 (light purple)
  114. "#FFE5CC", # Level 5 (light orange)
  115. "#D9CCAA", # Level 6 (light brown)
  116. "#FFD9E5", # Level 7 (light pink)
  117. "#FFFFCC", # Level 8 (light yellow)
  118. "#CCFFFF" # Level 9+ (light cyan)
  119. ]
  120. # Select color based on level
  121. color = colors[level] if level < len(colors) else "gray"
  122. # Draw the square with light colored fill (simulating transparency) and darker outline
  123. outline_color = color.replace("#", "") # Get hex color without #
  124. # Darken the outline color by reducing RGB values
  125. if len(outline_color) == 6:
  126. # Convert hex to RGB, then darken
  127. r = max(0, int(outline_color[0:2], 16) - 60)
  128. g = max(0, int(outline_color[2:4], 16) - 60)
  129. b = max(0, int(outline_color[4:6], 16) - 60)
  130. dark_outline = f"#{r:02x}{g:02x}{b:02x}"
  131. else:
  132. dark_outline = "black" # fallback
  133. # Store the original color for flashing
  134. self.original_colors[number] = color
  135. # Create a semi-transparent effect using stipple pattern
  136. # This creates a checkerboard-like pattern that simulates transparency
  137. rect_id = self.canvas.create_rectangle(x, y, x + size, y + size, outline=dark_outline, fill=color, width=1, stipple='gray50')
  138. # Tag the rectangle with the number for easy identification
  139. self.canvas.addtag_withtag(f"square_{number}", rect_id)
  140. # Draw a line segment at the bottom edge of the square (inside the square) only if size >= 50
  141. if size >= 50:
  142. line_y = y + size - 20 # Just above the bottom edge of the square
  143. self.canvas.create_line(x, line_y, x + size, line_y, fill="black", width=1)
  144. # Draw the name between the square and the line
  145. name_y = y + size - 10 # Position the name between square and line
  146. self.canvas.create_text(x + size // 2, name_y, text=name, fill="black", font=("Arial", 8))
  147. # Record the square's position information
  148. self.square_positions[number] = (x, y, size, parent_number)
  149. def get_square_level(self, number):
  150. """
  151. Calculate the level of a square in the hierarchy.
  152. Level 0: Main square (number 0)
  153. Level 1: Direct children of main square
  154. Level 2: Children of level 1 squares
  155. etc.
  156. """
  157. if number == 0:
  158. return 0
  159. level = 1 # Start at level 1 since we're past the main square
  160. current_parent = self.squares[number][3] # Get parent number from (x, y, size, parent_number)
  161. while current_parent is not None:
  162. level += 1
  163. if current_parent == 0:
  164. break
  165. current_parent = self.squares[current_parent][3] # Get parent's parent
  166. return level
  167. def check_clipboard_content(self):
  168. """Check clipboard content and highlight matching files"""
  169. try:
  170. # Get clipboard content
  171. clipboard_content = self.root.clipboard_get()
  172. # Find all files that contain the clipboard content
  173. matching_squares = set()
  174. if clipboard_content and len(clipboard_content.strip()) > 0:
  175. matching_squares = self.search_files_for_content(clipboard_content)
  176. # Update highlighted squares
  177. old_highlighted = self.highlighted_squares.copy()
  178. self.highlighted_squares = matching_squares
  179. # If there are changes, update the highlighted squares
  180. if old_highlighted != self.highlighted_squares:
  181. # Update the colors of highlighted squares
  182. self.update_highlighted_squares()
  183. except tk.TclError:
  184. # Clipboard is empty or contains non-text data
  185. old_highlighted = self.highlighted_squares.copy()
  186. self.highlighted_squares = set()
  187. # Restore colors of previously highlighted squares
  188. for square_num in old_highlighted:
  189. self.restore_square_color(square_num)
  190. # Schedule next check
  191. self.root.after(1000, self.check_clipboard_content) # Check every second
  192. def restore_square_color(self, square_num):
  193. """Restore the original color of a square"""
  194. if square_num in self.squares:
  195. x, y, size, parent_number = self.squares[square_num]
  196. # Find the rectangle item for this square
  197. tags = f"square_{square_num}"
  198. items = self.canvas.find_withtag(tags)
  199. # Restore original color
  200. original_color = self.original_colors.get(square_num, "#FFFFFF")
  201. for item in items:
  202. if "rectangle" in str(self.canvas.type(item)):
  203. self.canvas.itemconfig(item, fill=original_color)
  204. def stop_flashing(self):
  205. """Stop the highlighting (placeholder function)"""
  206. pass
  207. def search_files_for_content(self, search_content):
  208. """Search all text files for the given content and return matching square numbers"""
  209. import os
  210. matching_squares = set()
  211. # Only search files (not directories) that contain the search content
  212. if hasattr(self, 'number_to_path'):
  213. for number, path in self.number_to_path.items():
  214. # Double-check that this is a file, not a directory
  215. # Using both isfile() and not isdir() for extra safety
  216. # Double-check that this is a file, not a directory
  217. # Using both isfile() and not isdir() for extra safety
  218. if os.path.isfile(path) and not os.path.isdir(path):
  219. try:
  220. # Try to read the file as text
  221. with open(path, 'r', encoding='utf-8', errors='ignore') as f:
  222. content = f.read()
  223. if search_content.lower() in content.lower():
  224. matching_squares.add(number)
  225. except Exception:
  226. # If we can't read the file, skip it
  227. continue
  228. return matching_squares
  229. def update_highlighted_squares(self):
  230. """Update the color of highlighted squares to red"""
  231. # First, restore all previously highlighted squares to their original color
  232. for square_num in self.all_highlighted_squares:
  233. if square_num in self.squares:
  234. x, y, size, parent_number = self.squares[square_num]
  235. # Find the rectangle item for this square
  236. tags = f"square_{square_num}"
  237. items = self.canvas.find_withtag(tags)
  238. # Restore original color
  239. original_color = self.original_colors.get(square_num, "#FFFFFF")
  240. for item in items:
  241. if "rectangle" in str(self.canvas.type(item)):
  242. self.canvas.itemconfig(item, fill=original_color)
  243. # Now highlight the new matching squares in red
  244. for square_num in self.highlighted_squares:
  245. if square_num in self.squares:
  246. x, y, size, parent_number = self.squares[square_num]
  247. # Find the rectangle item for this square
  248. tags = f"square_{square_num}"
  249. items = self.canvas.find_withtag(tags)
  250. # Change color to red
  251. for item in items:
  252. if "rectangle" in str(self.canvas.type(item)):
  253. self.canvas.itemconfig(item, fill="red")
  254. # Update the set of all highlighted squares
  255. self.all_highlighted_squares = self.highlighted_squares.copy()
  256. def find_square_items_by_position(self, x, y, size):
  257. """Find canvas item IDs that correspond to a square at the given position"""
  258. # Find items by coordinates
  259. items = []
  260. overlapping = self.canvas.find_overlapping(x, y, x + size, y + size)
  261. for item in overlapping:
  262. # Check if this item is part of a square we drew
  263. tags = self.canvas.gettags(item)
  264. if any('square' in tag.lower() for tag in tags) or len(tags) == 0: # Assume squares have no special tags or 'square' in tag
  265. items.append(item)
  266. return items
  267. def run(self):
  268. self.root.mainloop()
  269. def folder_to_config(folder_path, skip_folder=[]):
  270. """
  271. 根据文件夹路径生成配置列表,表示每个文件夹下的子项数量。
  272. Args:
  273. folder_path: 要分析的根文件夹路径
  274. skip_folder: 不需要遍历的文件夹名称列表
  275. Returns:
  276. tuple: (config_list, path_info_dict, path_mapping_dict)
  277. config_list: 包含(父级编号, 子项数量)元组的列表
  278. (None, n) 表示根目录下有n个子项
  279. path_info_dict: 映射编号到文件/文件夹名称的字典
  280. path_mapping_dict: 映射编号到完整文件路径的字典
  281. """
  282. import os
  283. # 存储路径到编号的映射
  284. path_to_number = {}
  285. number_to_name = {}
  286. number_to_path = {} # 新增:编号到路径的映射
  287. # 首先为根目录分配编号
  288. path_to_number[folder_path] = 0
  289. number_to_name[0] = os.path.basename(folder_path)
  290. number_to_path[0] = folder_path # 添加路径映射
  291. # 使用栈进行迭代式深度优先遍历,确保正确处理父子关系
  292. stack = [folder_path]
  293. while stack:
  294. current_path = stack.pop()
  295. try:
  296. items = os.listdir(current_path)
  297. # 获取当前目录的直接子项(过滤掉需要跳过的目录)
  298. direct_children = []
  299. for item in items:
  300. item_path = os.path.join(current_path, item)
  301. # 检查是否是需要跳过的目录
  302. if os.path.isdir(item_path) and os.path.basename(item_path) in skip_folder:
  303. continue
  304. direct_children.append(item_path)
  305. # 为所有直接子项分配编号
  306. for item_path in direct_children:
  307. if item_path not in path_to_number:
  308. number = len(path_to_number)
  309. path_to_number[item_path] = number
  310. number_to_name[number] = os.path.basename(item_path)
  311. number_to_path[number] = item_path # 添加路径映射
  312. # 如果是目录且不在跳过列表中,加入栈中待处理
  313. if os.path.isdir(item_path):
  314. stack.append(item_path)
  315. except PermissionError:
  316. # 如果没有权限访问某个文件夹,则跳过
  317. continue
  318. # 构建结果 - 计算每个目录的直接子项数量
  319. result = []
  320. # 添加根目录信息
  321. root_items = []
  322. try:
  323. items = os.listdir(folder_path)
  324. for item in items:
  325. item_path = os.path.join(folder_path, item)
  326. if not (os.path.isdir(item_path) and os.path.basename(item_path) in skip_folder):
  327. root_items.append(item_path)
  328. except PermissionError:
  329. pass
  330. result.append((None, len(root_items))) # 根目录用None表示
  331. # 为每个目录添加信息(除了根目录)
  332. for path in path_to_number:
  333. if path != folder_path and os.path.isdir(path): # 排除根目录,只处理子目录
  334. try:
  335. items = os.listdir(path)
  336. # 计算当前目录的直接子项数量(过滤掉需要跳过的目录)
  337. child_count = 0
  338. for item in items:
  339. item_path = os.path.join(path, item)
  340. if not (os.path.isdir(item_path) and os.path.basename(item_path) in skip_folder):
  341. child_count += 1
  342. parent_number = path_to_number[path]
  343. result.append((parent_number, child_count))
  344. except PermissionError:
  345. # 如果无法访问目录,子项数量为0
  346. parent_number = path_to_number[path]
  347. result.append((parent_number, 0))
  348. return result, number_to_name, number_to_path
  349. if __name__ == "__main__":
  350. config, path_info, path_mapping = folder_to_config(r"E:\agricultural_research_platform", skip_folder=["automatedDeployment", "node_modules", ".vscode", ".git", "images"])
  351. print(config)
  352. app = SquareWindow(config, path_info, path_mapping)
  353. app.run()