{"behavior":"degenerate_repetition","label_filter":null,"limit":10,"next_offset":33328,"offset":33318,"previous_offset":null,"rows":[{"assistant_initiates":false,"asst_text":"\u0628\u0644\u0647\u060c \u0644\u0637\u0641\u0627\u064b \u0645\u062a\u0646 \u062e\u0648\u062f \u0631\u0627 \u0627\u0631\u0633\u0627\u0644 \u06a9\u0646\u06cc\u062f \u062a\u0627 \u0628\u0631\u0631\u0633\u06cc \u06a9\u0646\u0645.","behavior":"degenerate_repetition","conversation_hash":"00994143303ec41a0891cec6221db27d","conversation_redacted":false,"conversation_toxic":false,"country":"Australia","empty_output":false,"evidence_codes":[],"label":"absent","label_present":false,"label_qualified":false,"label_source":"Final dense release","language":"Persian","model":"gpt-4o-2024-08-06","n_chars":158,"n_turns_in_conversation":5,"n_words":20,"source_row_offset":33318,"source_shard":"turns-00038.parquet","timestamp":"Thu, 24 Oct 2024 14:22:14 GMT","turn_id":"87b73226b0cdc47e3f5cb007","turn_index":0,"user_text":"\u06cc\u0647 \u0645\u062a\u0646\u06cc \u0628\u0631\u0627\u062a \u0645\u06cc\u0641\u0631\u0633\u062a\u0645 \u0628\u0647 \u0627\u0646\u06af\u0644\u06cc\u0633\u06cc \u0645\u06cc\u0634\u0647 \u0686\u06a9\u0634 \u06a9\u0646\u06cc \u0628\u0631\u0627\u0645\r\n                            \r\n                            \r\n"},{"assistant_initiates":false,"asst_text":"\u062e\u0648\u0627\u0647\u0634 \u0645\u06cc\u200c\u06a9\u0646\u0645! \u0647\u0631 \u0632\u0645\u0627\u0646 \u06a9\u0647 \u0622\u0645\u0627\u062f\u0647 \u0628\u0648\u062f\u06cc\u062f \u0645\u062a\u0646 \u062e\u0648\u062f \u0631\u0627 \u0627\u0631\u0633\u0627\u0644 \u06a9\u0646\u06cc\u062f \u062a\u0627 \u0628\u0631\u0631\u0633\u06cc \u06a9\u0646\u0645.","behavior":"degenerate_repetition","conversation_hash":"00994143303ec41a0891cec6221db27d","conversation_redacted":false,"conversation_toxic":false,"country":"Australia","empty_output":false,"evidence_codes":[],"label":"absent","label_present":false,"label_qualified":false,"label_source":"Final dense release","language":"Persian","model":"gpt-4o-2024-08-06","n_chars":139,"n_turns_in_conversation":5,"n_words":16,"source_row_offset":33319,"source_shard":"turns-00038.parquet","timestamp":"Thu, 24 Oct 2024 14:22:14 GMT","turn_id":"0e50d59e0e48da932373d44b","turn_index":1,"user_text":"\u0645\u0631\u0633\u06cc\r\n                            \r\n                            \r\n"},{"assistant_initiates":false,"asst_text":"Thanks for sharing your text! Here\u2019s a revised version with some corrections and improvements:\n\nOne of my memorable experiences is from the time I went to Shiraz. Shiraz is one of the most beautiful and historical places in Iran, attracting many tourists every year. One thing that stood out to me was how relaxed the tourists were in terms of clothing, and they wore no makeup. They were more eager to learn and see historical sites rather than taking photos, which was quite the opposite of many Iranians, who didn\u2019t know the history of many places accurately and were more focused on taking pictures. The tourists seemed to care more about those places than we did.\n\nIf you have any other parts you'd like me to check or further questions, just let me know!","behavior":"degenerate_repetition","conversation_hash":"00994143303ec41a0891cec6221db27d","conversation_redacted":false,"conversation_toxic":false,"country":"Australia","empty_output":false,"evidence_codes":[],"label":"absent","label_present":false,"label_qualified":false,"label_source":"Final dense release","language":"Persian","model":"gpt-4o-2024-08-06","n_chars":1398,"n_turns_in_conversation":5,"n_words":240,"source_row_offset":33320,"source_shard":"turns-00038.parquet","timestamp":"Thu, 24 Oct 2024 14:22:14 GMT","turn_id":"4f5b42695ff35c4f2cdd5255","turn_index":2,"user_text":"One of my memorable experiences is referred to time when I had gone to Shiraz. Shiraz is one if the most beautiful and historical places in Iran so many tourists come to Iran every year. \u0645\u0646\u0638\u0648\u0631\u0645 \u0627\u06cc\u0646\u0647 \u06a9\u0647 \u0647\u0631\u0633\u0627\u0644\u0647 \u062a\u0648\u0631\u06cc\u0633\u062a \u0647\u0627\u06cc \u0632\u06cc\u0627\u062f\u06cc \u0628\u0647 \u0627\u06cc\u0631\u0627\u0646 \u0645\u06cc\u0627\u0646. One thing that contract me was how tourists had come\nthere. \u0645\u06cc\u062e\u0648\u0627\u0645 \u0628\u06af\u0645 \u0627\u0648\u0646\u0627 \u062e\u06cc\u0644\u06cc \u0631\u0627\u062d\u062a \u0644\u0628\u0627\u0633 \u067e\u0648\u0634\u06cc\u062f\u0647 \u0628\u0648\u062f\u0646\u062f \u0648\u0647\u06cc\u0686 \u0639\u0631\u0627\u06cc\u0634\u06cc \u0646\u062f\u0627\u0634\u062a\u0646\u062f. \u0648\u0628\u06cc\u0634\u062a\u0631 \u0645\u0634\u062a\u0627\u0642 \u06cc\u0627\u062f\u06a9\u06cc\u0631\u06cc \u0648\u062f\u06cc\u062f\u0646 \u0645\u0646\u0627\u0638\u0631 \u062a\u0627\u0631\u06cc\u062e\u06cc \u0628\u0648\u062f\u0646\u062f \u062a\u0627 \u0639\u06a9\u0633 \u06af\u0631\u0641\u062a\u0646 \u062f\u0642\u06cc\u0642\u0627 \u0628\u0631\u0639\u06a9\u0633 \u0627\u06cc\u0631\u0627\u0646\u06cc \u0647\u0627 \u06a9\u0647 \u0627\u0635\u0644\u0627 \u062a\u0627\u0631\u06cc\u062e\u0686\u0647 \u062e\u06cc\u0644\u06cc \u0627\u0632\u0645\u06a9\u0627\u0646 \u0647\u0627 \u0631\u0648 \u0628\u0647 \u062f\u0631\u0633\u062a\u06cc \u0646\u0645\u06cc\u062f\u0648\u0646\u0633\u062a\u0646 \u0648\u0628\u06cc\u0634\u062a\u0631 \u0628\u0647 \u0641\u0645\u0631 \u0639\u06a9\u0633 \u0627\u0646\u062f\u0627\u062e\u062a\u0646 \u0628\u062f\u0648\u0646\u062f \u0622\u0646\u0647\u0627 \u0628\u06cc\u0634\u062a\u0631 \u0627\u0632 \u0645\u0627 \u0628\u0647 \u0622\u0646 \u0645\u06a9\u0627\u0646\u0647\u0627 \u0627\u0647\u0645\u06cc\u062a \u0645\u06cc\u062f\u0627\u062f\u0646\u062f\r\n                            \r\n                            \r\n"},{"assistant_initiates":false,"asst_text":"\u062e\u0648\u0627\u0647\u0634 \u0645\u06cc\u200c\u06a9\u0646\u0645! \u0627\u06af\u0631 \u0633\u0648\u0627\u0644 \u06cc\u0627 \u0645\u062a\u0646 \u062f\u06cc\u06af\u0631\u06cc \u062f\u0627\u0631\u06cc\u062f \u06a9\u0647 \u0646\u06cc\u0627\u0632 \u0628\u0647 \u0628\u0631\u0631\u0633\u06cc \u062f\u0627\u0631\u062f\u060c \u062d\u062a\u0645\u0627\u064b \u0628\u0647 \u0645\u0646 \u0628\u06af\u0648\u06cc\u06cc\u062f. \u062e\u0648\u0634\u062d\u0627\u0644 \u0645\u06cc\u200c\u0634\u0648\u0645 \u06a9\u0647 \u06a9\u0645\u06a9 \u06a9\u0646\u0645!","behavior":"degenerate_repetition","conversation_hash":"00994143303ec41a0891cec6221db27d","conversation_redacted":false,"conversation_toxic":false,"country":"Australia","empty_output":false,"evidence_codes":[],"label":"absent","label_present":false,"label_qualified":false,"label_source":"Final dense release","language":"Persian","model":"gpt-4o-2024-08-06","n_chars":187,"n_turns_in_conversation":5,"n_words":25,"source_row_offset":33321,"source_shard":"turns-00038.parquet","timestamp":"Thu, 24 Oct 2024 14:22:14 GMT","turn_id":"fa6e53321e250835485192d6","turn_index":3,"user_text":"\u0645\u0645\u0646\u0648\u0646 \u0639\u0632\u06cc\u0632 \u062f\u0644\u0645\r\n                            \r\n                            \r\n"},{"assistant_initiates":false,"asst_text":"\u0646\u0647 \u062e\u0648\u0627\u0647\u0634 \u0645\u06cc\u200c\u06a9\u0646\u0645\u060c \u0627\u0646\u062c\u0627\u0645 \u0648\u0638\u06cc\u0641\u0647 \u0628\u0648\u062f. \u0627\u06af\u0631 \u0646\u06cc\u0627\u0632 \u0628\u0647 \u06a9\u0645\u06a9 \u0628\u06cc\u0634\u062a\u0631\u06cc \u062f\u0627\u0631\u06cc\u062f \u06cc\u0627 \u0633\u0648\u0627\u0644\u06cc \u062f\u0627\u0631\u06cc\u062f\u060c \u062f\u0631 \u062e\u062f\u0645\u062a \u0647\u0633\u062a\u0645!","behavior":"degenerate_repetition","conversation_hash":"00994143303ec41a0891cec6221db27d","conversation_redacted":false,"conversation_toxic":false,"country":"Australia","empty_output":false,"evidence_codes":[],"label":"absent","label_present":false,"label_qualified":false,"label_source":"Final dense release","language":"Persian","model":"gpt-4o-2024-08-06","n_chars":165,"n_turns_in_conversation":5,"n_words":20,"source_row_offset":33322,"source_shard":"turns-00038.parquet","timestamp":"Thu, 24 Oct 2024 14:22:14 GMT","turn_id":"bf375cca30ef04de685f5e6f","turn_index":4,"user_text":"\u0632\u062d\u0645\u062a \u06a9\u0634\u06cc\u062f\u06cc\r\n                            \r\n                            \r\n"},{"assistant_initiates":false,"asst_text":"\u0421\u0435\u0433\u043e\u0434\u043d\u044f \u0432\u0435\u0447\u0435\u0440\u043e\u043c \u043c\u043e\u0439 \u043c\u0443\u0436, \u043a\u0430\u0436\u0435\u0442\u0441\u044f, \u0443\u043b\u043e\u0436\u0438\u043b \u043d\u0430\u0448\u0435\u0433\u043e \u0441\u044b\u043d\u0430 \u0441\u043f\u0430\u0442\u044c \u043d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u043c \u0441\u043f\u043e\u0441\u043e\u0431\u043e\u043c. \u0415\u0433\u043e \u043f\u043e\u0434\u0445\u043e\u0434 \u0431\u044b\u043b \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u043d\u0435\u0432\u0435\u0440\u043d\u044b\u043c. \u042f \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u043b\u0430, \u0447\u0442\u043e \u0437\u0430\u0449\u0438\u0449\u0430\u044e \u0441\u0432\u043e\u0435\u0433\u043e \u0441\u044b\u043d\u0430. \u042f \u0441\u043a\u0430\u0437\u0430\u043b\u0430 \u043c\u0443\u0436\u0443, \u0447\u0442\u043e \u0442\u043e, \u0447\u0442\u043e \u043e\u043d \u0441\u0434\u0435\u043b\u0430\u043b, \u043d\u0435 \u0441\u043f\u043e\u0441\u043e\u0431, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043e\u0449\u0440\u0438\u0442\u044c \u043d\u0430\u0448\u0435\u0433\u043e \u0441\u044b\u043d\u0430.\n\n\u0412\u043c\u0435\u0441\u0442\u043e \u0442\u043e\u0433\u043e \u0447\u0442\u043e\u0431\u044b \u043d\u0430\u0431\u043b\u044e\u0434\u0430\u0442\u044c \u0437\u0430 \u0433\u0440\u0443\u0431\u044b\u043c \u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u0435\u043c \u043c\u0443\u0436\u0430, \u044f \u0440\u0435\u0448\u0438\u043b\u0430 \u0441\u0435\u0441\u0442\u044c \u0440\u044f\u0434\u043e\u043c \u0441 \u0441\u044b\u043d\u043e\u043c, \u0438 \u0432\u043c\u0435\u0441\u0442\u0435 \u043c\u044b \u0437\u0430\u043d\u044f\u043b\u0438\u0441\u044c \u0435\u0433\u043e \u0437\u0430\u0434\u0430\u043d\u0438\u0435\u043c \u043f\u043e \u0435\u0441\u0442\u0435\u0441\u0442\u0432\u043e\u0437\u043d\u0430\u043d\u0438\u044e, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043e\u0449\u0440\u0438\u0442\u044c \u0435\u0433\u043e \u0443\u0447\u0438\u0442\u044c\u0441\u044f. \u041c\u0443\u0436 \u0440\u0430\u0441\u0441\u0435\u0440\u0434\u0438\u043b\u0441\u044f, \u043d\u0430\u0447\u0430\u043b \u0440\u0443\u0433\u0430\u0442\u044c\u0441\u044f \u043d\u0430 \u043c\u0435\u043d\u044f \u0438 \u0443\u0433\u0440\u043e\u0436\u0430\u0442\u044c. \u042f \u0437\u043d\u0430\u043b\u0430, \u0447\u0442\u043e \u043e\u043d \u043d\u0435 \u0448\u0443\u0442\u0438\u0442. \u0415\u0433\u043e \u0441\u043b\u043e\u0432\u0430 \u043c\u0435\u043d\u044f \u0441\u0438\u043b\u044c\u043d\u043e \u043d\u0430\u043f\u0443\u0433\u0430\u043b\u0438!","behavior":"degenerate_repetition","conversation_hash":"2f4f5344b78b6643b822996b70e3999c","conversation_redacted":false,"conversation_toxic":false,"country":"United States","empty_output":false,"evidence_codes":[],"label":"absent","label_present":false,"label_qualified":false,"label_source":"Final dense release","language":"English","model":"gpt-4o-2024-08-06","n_chars":1144,"n_turns_in_conversation":1,"n_words":195,"source_row_offset":33323,"source_shard":"turns-00038.parquet","timestamp":"Thu, 24 Oct 2024 14:22:20 GMT","turn_id":"b8b638e714e67061204c09da","turn_index":0,"user_text":"System: \u0422\u044b - \u0443\u043c\u043d\u044b\u0439 \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0447\u0438\u043a \u0441 \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u043e\u0433\u043e \u044f\u0437\u044b\u043a\u0430 \u043d\u0430 \u0440\u0443\u0441\u0441\u043a\u0438\u0439.\nUser: \u0417\u0430\u0434\u0430\u0447\u0430: \u041f\u0435\u0440\u0435\u0432\u0435\u0434\u0438 \u0442\u0435\u043a\u0441\u0442 \u043d\u0430 \u0440\u0443\u0441\u0441\u043a\u0438\u0439 \u044f\u0437\u044b\u043a\nUser: \u0422\u0435\u043a\u0441\u0442\u044b: Tonight, my husband seemed to put our son down through an incorrect approach. His approach was perfectly wrong. I found myself defending my son. I told him that what he did was not the way to encourage our son.\r\nInstead of watching my husband\u2019s harsh behavior I decided to sit beside my son and, together, we worked on his science assignment in order to encourage him to study. My husband got upset and he was swearing at me. He threatened me. I knew that he was not joking. His words scared me a lot!\nAssistant:"},{"assistant_initiates":false,"asst_text":"\u041a\u0430\u043a \u044f \u043c\u043e\u0433\u0443 \u0441\u043d\u043e\u0432\u0430 \u0434\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u0436\u0435\u043d\u0449\u0438\u043d\u0430\u043c? \u042f \u043f\u043e\u0441\u0442\u043e\u044f\u043d\u043d\u043e \u043f\u044b\u0442\u0430\u044e\u0441\u044c \u0447\u0438\u0442\u0430\u0442\u044c \u043c\u0435\u0436\u0434\u0443 \u0441\u0442\u0440\u043e\u043a \u0441 \u043a\u0430\u0436\u0434\u043e\u0439 \u043d\u043e\u0432\u043e\u0439 \u0436\u0435\u043d\u0449\u0438\u043d\u043e\u0439, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u0432\u0441\u0442\u0440\u0435\u0447\u0430\u044e. \u041c\u043d\u0435 \u043a\u0440\u0430\u0439\u043d\u0435 \u0441\u043b\u043e\u0436\u043d\u043e \u043d\u0430\u043b\u0430\u0434\u0438\u0442\u044c \u0445\u043e\u0442\u044c \u043a\u0430\u043a\u0443\u044e-\u0442\u043e \u0441\u0432\u044f\u0437\u044c \u0441 \u043a\u0435\u043c-\u043b\u0438\u0431\u043e \u0438\u0437-\u0437\u0430 \u0435\u0451 \u043e\u0431\u043c\u0430\u043d\u0430 \u0438 \u0433\u043e\u0442\u043e\u0432\u043d\u043e\u0441\u0442\u0438 \u0434\u0435\u043b\u0430\u0442\u044c \u0438 \u0433\u043e\u0432\u043e\u0440\u0438\u0442\u044c \u0431\u0443\u043a\u0432\u0430\u043b\u044c\u043d\u043e \u0432\u0441\u0451, \u0447\u0442\u043e\u0431\u044b \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043c\u043e\u0438 \u044d\u043c\u043e\u0446\u0438\u0438. \u041a\u043e\u0433\u0434\u0430 \"\u043e\u0442\u043d\u043e\u0448\u0435\u043d\u0438\u044f\" \u0437\u0430\u043a\u043e\u043d\u0447\u0438\u043b\u0438\u0441\u044c, \u043e\u043d\u0430 \u0441\u0442\u0430\u043b\u0430 \u043a\u0440\u0430\u0439\u043d\u0435 \u0430\u0433\u0440\u0435\u0441\u0441\u0438\u0432\u043d\u043e\u0439 \u0438 \u043f\u044b\u0442\u0430\u043b\u0430\u0441\u044c \u0437\u0430\u043f\u0443\u0433\u0430\u0442\u044c \u043c\u0435\u043d\u044f, \u0447\u0442\u043e\u0431\u044b \u044f \u043c\u043e\u043b\u0447\u0430\u043b \u043e \u043c\u043d\u043e\u0433\u043e\u0447\u0438\u0441\u043b\u0435\u043d\u043d\u044b\u0445 \u043b\u043e\u0436\u043d\u044b\u0445 \u0437\u0430\u044f\u0432\u043b\u0435\u043d\u0438\u044f\u0445, \u0441\u0434\u0435\u043b\u0430\u043d\u043d\u044b\u0445 \u0432 \u0435\u0451 \u0438\u043c\u043c\u0438\u0433\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0439 \u0430\u043d\u043a\u0435\u0442\u0435.","behavior":"degenerate_repetition","conversation_hash":"34e9916ef6681620f4855ba6c9b64274","conversation_redacted":false,"conversation_toxic":false,"country":"United Kingdom","empty_output":false,"evidence_codes":[],"label":"absent","label_present":false,"label_qualified":false,"label_source":"Final dense release","language":"English","model":"gpt-4o-2024-08-06","n_chars":1062,"n_turns_in_conversation":1,"n_words":165,"source_row_offset":33324,"source_shard":"turns-00038.parquet","timestamp":"Thu, 24 Oct 2024 14:22:24 GMT","turn_id":"e080a8f753b89b86191e2e9b","turn_index":0,"user_text":"System: \u0422\u044b - \u0443\u043c\u043d\u044b\u0439 \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0447\u0438\u043a \u0441 \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u043e\u0433\u043e \u044f\u0437\u044b\u043a\u0430 \u043d\u0430 \u0440\u0443\u0441\u0441\u043a\u0438\u0439.\nUser: \u0417\u0430\u0434\u0430\u0447\u0430: \u041f\u0435\u0440\u0435\u0432\u0435\u0434\u0438 \u0442\u0435\u043a\u0441\u0442 \u043d\u0430 \u0440\u0443\u0441\u0441\u043a\u0438\u0439 \u044f\u0437\u044b\u043a\nUser: \u0422\u0435\u043a\u0441\u0442\u044b: How do I ever trust another woman? I have found myself constantly reading between the lines with every other woman that I meet. I am having a difficult time making any sort of connection to anyone because of her deception and willingness to say and do literally anything in order to control my emotions.\r\nOnce the \"relationship\" was over, she became extremely abusive and has attempted to intimidate me into silence regarding the many false claims made on her immigration application.\nAssistant:"},{"assistant_initiates":false,"asst_text":"The behavior of `SearchResultsProxyModel.mapFromSource` in the provided script is to map a given source index from the source model to the corresponding index in the proxy model, which is tasked with handling search results. It serves as an intermediary to reinterpret the data returned by a source model (like `CustomFileSystemModelForList`) so that it fits the requirements or format desired by another part of the application.\n\n### Analysis of `mapFromSource` Method:\n\n1. **Purpose**:  \n   `mapFromSource` is designed to translate a `QModelIndex` from the source model to the corresponding proxy index. This method is especially useful when you need to correlate data between your view and the underlying data model after some data has been filtered or manipulated by the proxy model.\n\n2. **Flow of Execution**:\n   - The method logs the receipt of a `sourceIndex`.\n   - Checks if `sourceIndex` is valid. If not, it returns an invalid proxy index (`QModelIndex()`).\n   - Retrieves the file path associated with the source index using the source model\u2019s `filePath` method.\n   - Normalizes the file path to ensure consistency in path representation.\n   - Uses the normalized file path to fetch the corresponding row from `_path_to_row`, a dictionary mapping file paths to row numbers.\n   - If a row is found (`row` is not `None`), it creates and returns a proxy index for that row and the given column.\n   - If no corresponding row is found, it logs a warning and returns an invalid `QModelIndex`.\n\n3. **Error Handling**:\n   - If the `sourceIndex` is not valid or the file path cannot be mapped to a row, the method returns an invalid `QModelIndex`.\n   - Warnings are logged if the method receives an invalid `sourceIndex`.\n\n4. **Logging**:\n   - Extensive logging allows tracking of when the method is called, source file paths involved, and mapping results, providing valuable information for debugging mapping issues between source and proxy models.\n\n5. **Cache Usage**:\n   - There is no use of caching in `mapFromSource` within this code snippet. Instead, caching is utilized in the `mapToSource` method (which wasn't fully covered in your request, but it is a complementary process) to potentially speed up reverse lookups.\n\n6. **Common Use-Cases**:\n   - When items in a list view need to be displayed differently based on search results, this method assists in aligning the visual representation with the actual data.\n   - It helps in ensuring that selections and operations performed in the view correctly correspond to the filtered dataset.\n\n7. **Execution Context**:\n   - If this method received invalid indices frequently, it suggests either an error in how indices are managed elsewhere or possible data races or faults in concurrent data access.\n   - Resulting proxy indices from this translation determine what the user sees after search operations, making this a crucial link in user interface operations.\n\nIn scenarios where debugging the behavior of `mapFromSource` is necessary, focus on ensuring that the source index is valid, paths are correctly normalized and mapped, and investigate any mismatches in what is expected versus what is represented in the rows of the proxy model.","behavior":"degenerate_repetition","conversation_hash":"a28792c70282341c86701e7b8bf4128f","conversation_redacted":false,"conversation_toxic":false,"country":"Canada","empty_output":false,"evidence_codes":[],"label":"absent","label_present":false,"label_qualified":false,"label_source":"Final dense release","language":"English","model":"gpt-4o-2024-08-06","n_chars":112795,"n_turns_in_conversation":1,"n_words":8941,"source_row_offset":33325,"source_shard":"turns-00038.parquet","timestamp":"Thu, 24 Oct 2024 14:22:29 GMT","turn_id":"52fa3eb0ac57554b30efccd7","turn_index":0,"user_text":"annalyse le comportement de mapFromSource\n\nLe chemin INDEX_DIR est valide.\n\nLe chemin WATCHED_DIR est valide.\n\n2024-10-24 10:20:03,106 - DEBUG - Schema defined with fields: ['creation_time', 'ext', 'modification_time', 'name', 'path', 'size']\n\n2024-10-24 10:20:03,107 - DEBUG - Initializing FileIndexer with index_dir: C:\\index_dir, rootPath: Y:\\DocSoufiane\n\n2024-10-24 10:20:03,107 - DEBUG - Beginning of __init__ function in CustomFileSystemModelForList\n\n2024-10-24 10:20:03,123 - DEBUG - ThumbnailCache: Initialis\u00e9 avec une taille maximale de 5000 vignettes.\n\n2024-10-24 10:20:03,133 - DEBUG - Successfully connected thumbnailReady signal to on_thumbnail_ready slot.\n\n2024-10-24 10:20:03,134 - DEBUG - End of __init__ function in CustomFileSystemModelForList\n\nLe chemin . est valide.\n\n2024-10-24 10:20:03,340 - DEBUG - Beginning of __init__ function in CustomFileSystemModelForList\n\n2024-10-24 10:20:03,343 - DEBUG - ThumbnailCache: Initialis\u00e9 avec une taille maximale de 5000 vignettes.\n\n2024-10-24 10:20:03,344 - DEBUG - Successfully connected thumbnailReady signal to on_thumbnail_ready slot.\n\n2024-10-24 10:20:03,344 - DEBUG - End of __init__ function in CustomFileSystemModelForList\n\n2024-10-24 10:20:03,344 - DEBUG - Start of setup_tree_dock_widget function\n\n2024-10-24 10:20:03,345 - INFO - Connection: tree_view.clicked -> on_tree_clicked\n\n2024-10-24 10:20:03,345 - INFO - Tree root path:\n\n2024-10-24 10:20:03,346 - DEBUG - Starting setup of the proxy model for the tree/Function setup_proxy_tree_model\n\n2024-10-24 10:20:03,347 - INFO - Source model assigned to the tree proxy.\n\n2024-10-24 10:20:03,347 - INFO - Filter of the tree proxy model invalidated to reflect the changes.\n\n2024-10-24 10:20:03,347 - DEBUG - End of the setup of the proxy model for the tree/Function setup_proxy_tree_model\n\n2024-10-24 10:20:03,349 - DEBUG - End of setup_tree_dock_widget function\n\n2024-10-24 10:20:03,349 - DEBUG - Starting setup of the proxy model for the tree/Function setup_proxy_tree_model\n\n2024-10-24 10:20:03,349 - DEBUG - Proxy model for the tree is already set up.\n\n2024-10-24 10:20:03,349 - DEBUG - SearchResultsProxyModel: setSourceModel called\n\n2024-10-24 10:20:03,350 - DEBUG - Starting setup of the proxy model for the file list./Function setup_file_list_proxy\n\n2024-10-24 10:20:03,350 - INFO - Source model assigned to the file list proxy.\n\n2024-10-24 10:20:03,350 - INFO - Filters applied for the file list proxy model: *.dxf *.jpg *.jpeg *.png *.bmp\n\n2024-10-24 10:20:03,350 - INFO - Filter of the file list proxy model invalidated to reflect the changes.\n\n2024-10-24 10:20:03,350 - DEBUG - End of the setup of the proxy model for the file list./Function setup_file_list_proxy\n\n2024-10-24 10:20:03,351 - DEBUG - Starting the setup_ui function in FileViewer\n\n2024-10-24 10:20:03,351 - DEBUG - Starting the setupTreeModelAndView function\n\n2024-10-24 10:20:03,352 - DEBUG - End of the setupTreeModelAndView function\n\n2024-10-24 10:20:03,352 - DEBUG - Starting the setupListModelAndView function\n\n2024-10-24 10:20:03,373 - INFO - Initial root path for the file list model: (empty)\n\n2024-10-24 10:20:03,373 - INFO - Proxy model configured with the file list model.\n\n2024-10-24 10:20:03,373 - INFO - File list view configured with the proxy model.\n\n2024-10-24 10:20:03,373 - INFO - File list view configured to display empty content.\n\n2024-10-24 10:20:03,373 - INFO - Triggered showModelInfo:\n\n2024-10-24 10:20:03,374 - DEBUG - End of the setupListModelAndView function\n\n2024-10-24 10:20:03,374 - DEBUG - Start of setup_central_frame function\n\n2024-10-24 10:20:03,376 - INFO - New icon size for QListView: PyQt5.QtCore.QSize(160, 120)\n\n2024-10-24 10:20:03,376 - DEBUG - End of setup_central_frame function\n\n2024-10-24 10:20:03,376 - DEBUG - Start of setup_preview_dock_widget function\n\n2024-10-24 10:20:03,378 - DEBUG - End of setup_preview_dock_widget function\n\n2024-10-24 10:20:03,379 - INFO - Connecting signals and slots:\n\n2024-10-24 10:20:03,379 - DEBUG - [Init] Directory label initialized\n\n2024-10-24 10:20:03,379 - DEBUG - [Layout] Directory label added to layout\n\n2024-10-24 10:20:03,379 - DEBUG - End of the setup_ui function in FileViewer\n\n2024-10-24 10:20:03,379 - INFO - Start setupMenu\n\n2024-10-24 10:20:03,379 - DEBUG - Setting up the menu bar\n\n2024-10-24 10:20:03,381 - DEBUG - Menu bar configured with File and View menus.\n\n2024-10-24 10:20:03,381 - INFO - End setupMenu\n\n2024-10-24 10:20:03,381 - INFO - Start setupToolbar\n\n2024-10-24 10:20:03,381 - DEBUG - Setting up the toolbar\n\n2024-10-24 10:20:03,381 - DEBUG - Toolbar configured\n\n2024-10-24 10:20:03,382 - INFO - End setupToolbar\n\n2024-10-24 10:20:03,382 - DEBUG - SearchWorker initialized with index_dir: C:\\index_dir\n\n2024-10-24 10:20:03,383 - INFO - Function update_directory_label start\n\n2024-10-24 10:20:03,383 - DEBUG - Attempting to update directory label with text: Current folder path:\n\n2024-10-24 10:20:03,383 - INFO - Updated directory label: Current folder path:\n\n2024-10-24 10:20:03,383 - INFO - Function update_directory_label end\n\n2024-10-24 10:20:03,384 - INFO - Connection: file_list.clicked -> display_selected_file\n\n2024-10-24 10:20:03,384 - INFO - Connection: file_list.doubleClicked -> on_folder_double_clicked\n\n2024-10-24 10:20:03,384 - DEBUG - SearchResultsProxyModel: setSourceModel called\n\n2024-10-24 10:20:03,439 - INFO - Start of resizeEvent in CustomGraphicsView class\n\n2024-10-24 10:20:03,439 - DEBUG - Resize event detected.\n\n2024-10-24 10:20:03,439 - DEBUG - Initial view adjusted to show entire scene.\n\n2024-10-24 10:20:03,439 - DEBUG - Signal connected to adjust view dynamically.\n\n2024-10-24 10:20:03,439 - INFO - End of resizeEvent in CustomGraphicsView class\n\n2024-10-24 10:20:03,440 - INFO - Start of showEvent in CustomGraphicsView class\n\n2024-10-24 10:20:03,440 - DEBUG - Show event detected.\n\n2024-10-24 10:20:03,440 - INFO - End of showEvent in CustomGraphicsView class\n\n2024-10-24 10:20:07,637 - INFO - Start of load_folder function\n\n2024-10-24 10:20:17,902 - INFO - Selected folder path: Y:/Divers fichiers Dxf \u00c0 TRIER/ARC\n\n2024-10-24 10:20:17,904 - INFO - Function update_directory_label start\n\n2024-10-24 10:20:17,904 - DEBUG - Attempting to update directory label with text: Current folder path: Y:/Divers fichiers Dxf \u00c0 TRIER/ARC\n\n2024-10-24 10:20:17,904 - INFO - Updated directory label: Current folder path: Y:/Divers fichiers Dxf \u00c0 TRIER/ARC\n\n2024-10-24 10:20:17,904 - INFO - Function update_directory_label end\n\n2024-10-24 10:20:17,904 - DEBUG - [load_folder] Current folder updated to: Y:/Divers fichiers Dxf \u00c0 TRIER/ARC\n\n2024-10-24 10:20:17,906 - INFO - End of load_folder function\n\n2024-10-24 10:20:17,958 - DEBUG - ThumbnailCache: __getitem__ appel\u00e9 pour Y:/Divers fichiers Dxf \u00c0 TRIER/ARC/REPARE LUM 4.5-6.5PO.DXF\n\n2024-10-24 10:20:17,959 - DEBUG - Thumbnail retrieved from cache for: Y:/Divers fichiers Dxf \u00c0 TRIER/ARC/REPARE LUM 4.5-6.5PO.DXF\n\n2024-10-24 10:20:17,959 - DEBUG - SearchResultsProxyModel.mapFromSource appel\u00e9 avec sourceIndex: <PyQt5.QtCore.QModelIndex object at 0x000002077CFD2C10>\n\n2024-10-24 10:20:17,959 - DEBUG - Source File Path: y:\\divers fichiers dxf \u00e0 trier\\arc\\repare lum 4.5-6.5po.dxf\n\n2024-10-24 10:20:17,959 - DEBUG - Mapped Row: None\n\n2024-10-24 10:20:17,959 - WARNING - SearchResultsProxyModel.mapFromSource re\u00e7u un sourceIndex invalide.\n\n2024-10-24 10:20:17,960 - DEBUG - SearchResultsProxyModel.mapFromSource appel\u00e9 avec sourceIndex: <PyQt5.QtCore.QModelIndex object at 0x000002077CFD2AC0>\n\n2024-10-24 10:20:17,960 - DEBUG - Source File Path: y:\\divers fichiers dxf \u00e0 trier\\arc\\repare lum 4.5-6.5po.dxf\n\n2024-10-24 10:20:17,960 - DEBUG - Mapped Row: None\n\n2024-10-24 10:20:17,960 - WARNING - SearchResultsProxyModel.mapFromSource re\u00e7u un sourceIndex invalide.\n\n2024-10-24 10:20:17,960 - DEBUG - SearchResultsProxyModel: Skipping dataChanged emission, model is not active.\n\n2024-10-24 10:20:17,961 - DEBUG - Fonction update_file_list_view: D\u00e9but\n\n2024-10-24 10:20:17,961 - ERROR - Le chemin du dossier doit \u00eatre une cha\u00eene de caract\u00e8res valide.\n\n2024-10-24 10:20:17,961 - DEBUG - ThumbnailCache: __getitem__ appel\u00e9 pour Y:/Divers fichiers Dxf \u00c0 TRIER/ARC/REPARE LUM 4.5-6.5PO.DXF\n\n2024-10-24 10:20:17,961 - DEBUG - Thumbnail retrieved from cache for: Y:/Divers fichiers Dxf \u00c0 TRIER/ARC/REPARE LUM 4.5-6.5PO.DXF\n\n2024-10-24 10:20:17,961 - DEBUG - SearchResultsProxyModel.mapFromSource appel\u00e9 avec sourceIndex: <PyQt5.QtCore.QModelIndex object at 0x000002077CFD2B30>\n\n2024-10-24 10:20:17,961 - DEBUG - Source File Path: y:\\divers fichiers dxf \u00e0 trier\\arc\\repare lum 4.5-6.5po.dxf\n\n2024-10-24 10:20:17,961 - DEBUG - Mapped Row: None\n\n2024-10-24 10:20:17,961 - WARNING - SearchResultsProxyModel.mapFromSource re\u00e7u un sourceIndex invalide.\n\n2024-10-24 10:20:17,962 - DEBUG - SearchResultsProxyModel.mapFromSource appel\u00e9 avec sourceIndex: <PyQt5.QtCore.QModelIndex object at 0x000002077CFD2AC0>\n\n2024-10-24 10:20:17,962 - DEBUG - Source File Path: y:\\divers fichiers dxf \u00e0 trier\\arc\\repare lum 4.5-6.5po.dxf\n\n2024-10-24 10:20:17,962 - DEBUG - Mapped Row: None\n\n2024-10-24 10:20:17,962 - WARNING - SearchResultsProxyModel.mapFromSource re\u00e7u un sourceIndex invalide.\n\n2024-10-24 10:20:17,962 - DEBUG - SearchResultsProxyModel: Skipping dataChanged emission, model is not active.\n\n2024-10-24 10:20:17,962 - DEBUG - Fonction update_file_list_view: D\u00e9but\n\n2024-10-24 10:20:17,962 - ERROR - Le chemin du dossier doit \u00eatre une cha\u00eene de caract\u00e8res valide.\n\n2024-10-24 10:20:20,475 - DEBUG - Slot activated: launch_delayed_search\n\n2024-10-24 10:20:20,475 - INFO - Search criteria modified. Waiting before launching search...\n\n2024-10-24 10:20:20,475 - DEBUG - End of launch_delayed_search\n\n2024-10-24 10:20:21,467 - DEBUG - Function launchSearch start\n\n2024-10-24 10:20:21,468 - DEBUG - Search criteria: r, criteria_type: filename\n\n2024-10-24 10:20:21,468 - DEBUG - Current root path: Y:/Divers fichiers Dxf \u00c0 TRIER/ARC\n\n2024-10-24 10:20:21,468 - DEBUG - WATCHED_DIR: Y:\\DocSoufiane\n\n2024-10-24 10:20:21,468 - DEBUG - Search criteria set: r (type: filename)\n\n2024-10-24 10:20:21,468 - DEBUG - Date range set: 2024-10-24 - 2024-10-24\n\n2024-10-24 10:20:21,469 - DEBUG - Base path for search set: Y:\\Divers fichiers Dxf \u00c0 TRIER\\ARC\n\n2024-10-24 10:20:21,469 - DEBUG - Function launchSearch end\n\n2024-10-24 10:20:21,469 - INFO - Starting search.\n\n2024-10-24 10:20:21,469 - DEBUG - Root path for search: Y:\\Divers fichiers Dxf \u00c0 TRIER\\ARC\n\n2024-10-24 10:20:21,470 - INFO - Using file system search.\n\n2024-10-24 10:20:21,470 - INFO - Finalizing search.\n\n2024-10-24 10:20:21,470 - INFO - Search completed with 1 results.\n\n2024-10-24 10:20:21,471 - DEBUG - Search finished with success: True\n\n2024-10-24 10:20:21,471 - DEBUG - Stopping search thread.\n\n2024-10-24 10:20:21,471 - DEBUG - Search thread stopped.\n\n2024-10-24 10:20:21,472 - DEBUG - Search thread stopped and resources cleaned up.\n\n2024-10-24 10:20:21,472 - DEBUG - Function handle_search_completion start\n\n2024-10-24 10:20:21,472 - INFO - Search completed successfully.\n\n2024-10-24 10:20:21,472 - DEBUG - Beginning of displayResults function.\n\n2024-10-24 10:20:21,472 - DEBUG - Updating proxy model with search results.\n\n2024-10-24 10:20:21,472 - DEBUG - SearchResultsProxyModel.setSearchResults called with 1 results.\n\n2024-10-24 10:20:21,472 - DEBUG - setSearchResults: Model reset completed.\n\n2024-10-24 10:20:21,472 - DEBUG - SearchResultsProxyModel set as the current model for file_list.\n\n2024-10-24 10:20:21,476 - DEBUG - SearchResultsProxyModel.mapFromSource appel\u00e9 avec sourceIndex: <PyQt5.QtCore.QModelIndex object at 0x000002077CFD2AC0>\n\n2024-10-24 10:20:21,476 - DEBUG - Source File Path: y:\\divers fichiers dxf \u00e0 trier\\arc\\repare lum 4.5-6.5po.dxf\n\n2024-10-24 10:20:21,476 - DEBUG - Mapped Row: 0\n\n2024-10-24 10:20:21,477 - DEBUG - SearchResultsProxyModel.mapFromSource appel\u00e9 avec sourceIndex: <PyQt5.QtCore.QModelIndex object at 0x000002077CFD2BA0>\n\n2024-10-24 10:20:21,478 - DEBUG - Source File Path: y:\\divers fichiers dxf \u00e0 trier\\arc\\repare lum 4.5-6.5po.dxf\n\n2024-10-24 10:20:21,478 - DEBUG - Mapped Row: 0\n\n2024-10-24 10:20:21,479 - DEBUG - Logging search results.\n\n2024-10-24 10:20:21,479 - DEBUG - Beginning of logSearchResults function.\n\n2024-10-24 10:20:21,479 - DEBUG - Search result: y:\\divers fichiers dxf \u00e0 trier\\arc\\REPARE LUM 4.5-6.5PO.DXF\n\n2024-10-24 10:20:21,480 - DEBUG - End of logSearchResults function.\n\n2024-10-24 10:20:21,480 - DEBUG - End of displayResults function.\n\n2024-10-24 10:20:21,480 - DEBUG - Function handle_search_completion end\n\n2024-10-24 10:20:21,480 - DEBUG - End of onSearchFinished function.\n\n2024-10-24 10:20:21,491 - DEBUG - ThumbnailCache: __getitem__ appel\u00e9 pour Y:/divers fichiers dxf \u00e0 trier/ARC/repare lum 4.5-6.5po.dxf\n\n2024-10-24 10:20:21,491 - DEBUG - Thumbnail retrieved from cache for: Y:/divers fichiers dxf \u00e0 trier/ARC/repare lum 4.5-6.5po.dxf\n\n2024-10-24 10:20:21,492 - DEBUG - SearchResultsProxyModel.mapFromSource appel\u00e9 avec sourceIndex: <PyQt5.QtCore.QModelIndex object at 0x00000207043D0210>\n\n2024-10-24 10:20:21,492 - DEBUG - Source File Path: y:\\divers fichiers dxf \u00e0 trier\\arc\\repare lum 4.5-6.5po.dxf\n\n2024-10-24 10:20:21,492 - DEBUG - Mapped Row: 0\n\n2024-10-24 10:20:21,492 - DEBUG - SearchResultsProxyModel.mapFromSource appel\u00e9 avec sourceIndex: <PyQt5.QtCore.QModelIndex object at 0x00000207043D01A0>\n\n2024-10-24 10:20:21,492 - DEBUG - Source File Path: y:\\divers fichiers dxf \u00e0 trier\\arc\\repare lum 4.5-6.5po.dxf\n\n2024-10-24 10:20:21,492 - DEBUG - Mapped Row: 0\n\n2024-10-24 10:20:21,493 - DEBUG - ThumbnailCache: __getitem__ appel\u00e9 pour Y:/divers fichiers dxf \u00e0 trier/ARC/repare lum 4.5-6.5po.dxf\n\n2024-10-24 10:20:21,493 - DEBUG - Thumbnail retrieved from cache for: Y:/divers fichiers dxf \u00e0 trier/ARC/repare lum 4.5-6.5po.dxf\n\n2024-10-24 10:20:21,494 - DEBUG - SearchResultsProxyModel.mapFromSource appel\u00e9 avec sourceIndex: <PyQt5.QtCore.QModelIndex object at 0x00000207043D0280>\n\n2024-10-24 10:20:21,494 - DEBUG - Source File Path: y:\\divers fichiers dxf \u00e0 trier\\arc\\repare lum 4.5-6.5po.dxf\n\n2024-10-24 10:20:21,494 - DEBUG - Mapped Row: 0\n\n2024-10-24 10:20:21,494 - DEBUG - SearchResultsProxyModel.mapFromSource appel\u00e9 avec sourceIndex: <PyQt5.QtCore.QModelIndex object at 0x00000207043D01A0>\n\n2024-10-24 10:20:21,494 - DEBUG - Source File Path: y:\\divers fichiers dxf \u00e0 trier\\arc\\repare lum 4.5-6.5po.dxf\n\n2024-10-24 10:20:21,494 - DEBUG - Mapped Row: 0\n\n#...debut du script\n\nclass CustomFileSystemModelForList(QFileSystemModel):\n\n\u00a0 \u00a0 \"\"\"Mod\u00e8le de syst\u00e8me de fichiers personnalis\u00e9 pour afficher les vignettes dans une vue de liste.\"\"\"\n\n\u00a0 \u00a0 searchFinished = pyqtSignal(list) # results: List[str]\n\n\u00a0 \u00a0 rootPathChanged = pyqtSignal(str) # new_root_path: str\n\n\u00a0 \u00a0 #dataChanged = pyqtSignal(QModelIndex, QModelIndex) # index: QModelIndex\u00a0\n\n\u00a0 \u00a0 modelUpdated = pyqtSignal(QModelIndex)\u00a0 # index: QModelIndex # Signal to notify that the model has been updated\n\n\u00a0 \u00a0 directoryLoaded = pyqtSignal(str) # path: str\n\n\u00a0 \u00a0 #thumbnailUpdated = pyqtSignal(str, QPixmap, bool) # file_path: str, thumbnail: QPixmap, success: bool\n\n\u00a0 \u00a0 thumbnailRequested = pyqtSignal(str)\u00a0 #\n\n\n\n\u00a0 \u00a0 def __init__(self, parent=None, indexer=None, max_workers=10, cache_size=5000):\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Beginning of __init__ function in CustomFileSystemModelForList\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 super().__init__(parent)\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.allowed_extensions = ALLOWED_EXTENSIONS\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.setRootPath(os.path.normpath(WATCHED_DIR))\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 # Connexion des signaux pour le d\u00e9bogage\n\n\u00a0 \u00a0 \u00a0 \u00a0 #self.searchFinished.connect(lambda results: print(f\"Search finished signal received with results: {results}\"))\n\n\u00a0 \u00a0 \u00a0 \u00a0 #self.rootPathChanged.connect(lambda path: print(f\"Root path changed signal received with path: {path}\"))\n\n\u00a0 \u00a0 \u00a0 \u00a0 #self.dataChanged.connect(lambda top_left, bottom_right: print(f\"Data changed signal received for indexes: {top_left}, {bottom_right}\"))\n\n\u00a0 \u00a0 \u00a0 \u00a0 #self.rootPathChanged.connect(lambda path: logger.debug(f\"Root path changed: {path}\"))\n\n\u00a0 \u00a0 \u00a0 \u00a0 #self.modelUpdated.connect(lambda index: print(f\"Model updated signal received for index: {index}\"))\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.thumbnailManager = ThumbnailManager(parent=self, max_workers=max_workers, cache_size=cache_size)\n\n\u00a0 \u00a0 \u00a0 \u00a0 # Connexion du signal thumbnailReady au slot correspondant de ThumbnailManager\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.thumbnailManager.thumbnailReady.connect(self.on_thumbnail_ready)\n\n\u00a0 \u00a0 \u00a0 \u00a0 # Connexion explicite du signal thumbnailRequested\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.thumbnailRequested.connect(self.thumbnailManager.start_worker)\n\n\u00a0 \u00a0 \u00a0 \u00a0 # Connexion du signal requestProcessed au slot correspondant de FileViewer\n\n\u00a0 \u00a0 \u00a0 \u00a0 #self.processing_thread.requestProcessed.connect(self.handle_request_processed)\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.parent = parent\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.file_list = QListView()\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.file_list.setStyleSheet(\"QListView::item { margin: 5px; }\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.criteria = \"\"\u00a0 \u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 self._search_timer = QTimer(self)\n\n\u00a0 \u00a0 \u00a0 \u00a0 self._search_timer.setSingleShot(True)\n\n\u00a0 \u00a0 \u00a0 \u00a0 self._last_criteria = \"\"\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.indexer = indexer\n\n\u00a0 \u00a0 \u00a0 \u00a0 if self.indexer:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # Connect the indexUpdated signal with an anonymous lambda function to provide rootPath\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.indexer.indexUpdated.connect(lambda: QTimer.singleShot(1000,\u00a0 # D\u00e9lai de 1 seconde\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.add_files_to_watcher))\n\n\u00a0 \u00a0 \u00a0 \u00a0 # Cache resources\n\n\u00a0 \u00a0 \u00a0 \u00a0 script_directory = os.path.dirname(os.path.abspath(__file__))\n\n\u00a0 \u00a0 \u00a0 \u00a0 icon_path = os.path.join(script_directory, \"dossier.png\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.folder_icon = QIcon(QPixmap(icon_path))\n\n\u00a0 \u00a0 \u00a0 \u00a0 if self.folder_icon.isNull():\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(f\"Failed to load folder icon from {icon_path}\")\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 #self.thumbnailManager = ThumbnailManager(parent=self, max_workers=max_workers, cache_size=cache_size) # Cr\u00e9er une instance de ThumbnailManager\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.thumbnailManager.thumbnailReady.connect(self.on_thumbnail_ready)\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Successfully connected thumbnailReady signal to on_thumbnail_ready slot.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.file_watcher = QFileSystemWatcher(self)\u00a0 # Initialize file_watcher\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.directoryLoaded.connect(self.on_directory_loaded)\n\n\u00a0 \u00a0 \u00a0 \u00a0 #self.file_watcher.fileChanged.connect(self.handle_file_change) # Connect to the signal\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"End of __init__ function in CustomFileSystemModelForList\")\n\n\n\n\u00a0 \u00a0 def event(self, event):\n\n\u00a0 \u00a0 \u00a0 \u00a0 return super().event(event)\u00a0 # Revenir \u00e0 la m\u00e9thode par d\u00e9faut\n\n\n\n\u00a0 \u00a0 def rowCount(self, parent=QModelIndex()):\n\n\u00a0 \u00a0 \u00a0 \u00a0 #logger.debug(f\"rowCount called with parent: {parent}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 return super().rowCount(parent)\n\n\n\n\u00a0 \u00a0 def index(self, *args, **kwargs):\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 #logger.debug(f\"index called with row: {row}, column: {column}, parent: {parent}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 try:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 #result = super().index(row, column, parent)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 #logger.debug(f\"CustomFileSystemModelForList.index: Returning index: {result}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if len(args) == 1 and isinstance(args[0], str):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # Appel avec path\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 path = args[0]\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 column = kwargs.get('column', 0)\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 #logger.debug(f\"index called with path: {path}, column: {column}\")\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return super().index(path, column)\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 #return super().index(row, column, parent)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 elif len(args) == 2 and isinstance(args[0], str) and isinstance(args[1], int):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # Appel avec path et column\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 path = args[0]\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 column = args[1]\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 #logger.debug(f\"index called with path: {path}, column: {column}\")\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return super().index(path, column)\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # Appel avec row, column, parent\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return super().index(*args, **kwargs)\u00a0\u00a0\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 except Exception as e:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(f\"CustomFileSystemModelForList.index: An error occurred: {e}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 raise\u00a0 # Remonte l'exception pour une gestion plus haut dans le code si n\u00e9cessaire\n\n\u00a0 \u00a0 def indexFromPath(self, path):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \"\"\"Helper function to get the index from a path.\"\"\"\n\n\u00a0 \u00a0 \u00a0 \u00a0 #logger.debug(f\"Entering CustomFileSystemModelForList.indexFromPath with path: {path}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 result = super().index(path)\n\n\u00a0 \u00a0 \u00a0 \u00a0 #logger.debug(f\"CustomFileSystemModelForList.indexFromPath: Returning index: {result}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 return result\n\n\u00a0 \u00a0 def add_files_to_watcher(self):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \"\"\"\n\n\u00a0 \u00a0 \u00a0 \u00a0 Ajoute les fichiers du r\u00e9pertoire surveill\u00e9 (WATCHED_DIR) et de ses sous-r\u00e9pertoires\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 au `QFileSystemWatcher` pour surveiller les modifications.\n\n\u00a0 \u00a0 \u00a0 \u00a0 \"\"\"\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"Entering CustomFileSystemModelForList.add_files_to_watcher\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 try:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if not self.file_watcher:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.warning(\"file_watcher is not initialized.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # Assurez-vous que le r\u00e9pertoire surveill\u00e9 est correctement normalis\u00e9\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 normalized_watched_dir = os.path.normpath(WATCHED_DIR)\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # Utiliser QDir pour lister les fichiers dans le r\u00e9pertoire surveill\u00e9\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 dir = QDir(normalized_watched_dir)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 dir.setNameFilters([f\"*{ext}\" for ext in self.allowed_extensions])\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 dir.setFilter(QDir.Files | QDir.NoSymLinks | QDir.NoDotAndDotDot)\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # Obtenir tous les fichiers correspondants dans le r\u00e9pertoire et ses sous-r\u00e9pertoires\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 file_info_list = dir.entryInfoList(QDir.AllEntries | QDir.NoDotAndDotDot, QDir.Name)\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 allowed_file_paths = []\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 for file_info in file_info_list:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 file_path_str = file_info.absoluteFilePath()\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 #logger.debug(f\"Checking file path: {file_path_str}\")\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # V\u00e9rifiez si le fichier est cach\u00e9 ou syst\u00e8me\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if file_info.isHidden() or (win32api.GetFileAttributes(file_path_str) & win32con.FILE_ATTRIBUTE_SYSTEM):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"\u00a0 - Skipped: Is hidden or system file\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 continue\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # V\u00e9rifier l'extension du fichier\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 extension = os.path.splitext(file_path_str)[1].lower()\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if extension not in self.allowed_extensions:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"\u00a0 - Skipped: Extension {extension} not allowed\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 continue\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 allowed_file_paths.append(file_path_str)\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if not allowed_file_paths:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.warning(\"No valid file paths found to add to QFileSystemWatcher.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.warning(f\"\u00a0 - Root path: {WATCHED_DIR}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.warning(f\"\u00a0 - Allowed extensions: {self.allowed_extensions}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.file_watcher.addPaths(allowed_file_paths)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.info(f\"{len(allowed_file_paths)} files added to QFileSystemWatcher.\")\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 except Exception as e:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.critical(f\"Unhandled exception in add_files_to_watcher: {e}\", exc_info=True)\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Exiting CustomFileSystemModelForList.add_files_to_watcher\")\n\n\u00a0 \u00a0\u00a0\n\n\u00a0 \u00a0 def on_directory_loaded(self, path):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \"\"\"\n\n\u00a0 \u00a0 \u00a0 \u00a0 G\u00e8re les actions \u00e0 effectuer apr\u00e8s le chargement d'un r\u00e9pertoire.\n\n\u00a0 \u00a0 \u00a0 \u00a0 \"\"\"\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.info(f\"on_directory_loaded / R\u00e9pertoire charg\u00e9 (CustomFileSystemModelForList) : {path}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 if self.indexer:\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 normalized_path = os.path.normpath(path)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if normalized_path.startswith(os.path.normpath(WATCHED_DIR)):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.indexer.buildIndex()\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"add_files_to_watcher.on_directory_loaded(self, path) / self.indexer.buildIndex()\u00a0 called\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 #self.add_files_to_watcher(path)\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.warning(\"CustomFileSystemModelForList/ on_directory_loaded; Indexer non disponible dans CustomFileSystemModelForList.\")\n\n\n\n\u00a0 \u00a0 def data(self, index, role=Qt.DisplayRole):\n\n\u00a0 \u00a0 \u00a0 \u00a0 #logger.debug(f\"CustomFileSystemModelForList.data called for index {index} with role {role}\")\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 if role in (Qt.SizeHintRole, Qt.FontRole):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return None\n\n\u00a0 \u00a0 \u00a0 \u00a0 try:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if not index.isValid():\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return None\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if role not in [Qt.DisplayRole, Qt.DecorationRole, Qt.ToolTipRole]:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return None\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return self.standard_data_method(index, role)\n\n\u00a0 \u00a0 \u00a0 \u00a0 except Exception as e:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.exception(f\"CustomFileSystemModelForList.data: Exception occurred: {e}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return None\n\n\u00a0 \u00a0 \u00a0 \u00a0 finally:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 pass\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 #logger.debug(f\"CustomFileSystemModelForList.data finished for index {index} with role {role}\")\n\n\n\n\u00a0 \u00a0 def standard_data_method(self, index, role):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \"\"\"M\u00e9thode standard pour fournir les donn\u00e9es du mod\u00e8le.\"\"\"\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 if role in (Qt.SizeHintRole, Qt.FontRole):\u00a0 # Optimisation : ignorer ces r\u00f4les\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return None\n\n\u00a0 \u00a0 \u00a0 \u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 file_path = self.filePath(index)\n\n\u00a0 \u00a0 \u00a0 \u00a0 #logger.debug(f\"CustomFileSystemModelForList.standard_data_method called for {file_path}, role: {role}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 #logger.debug(f\"standard_data_method: Current thread: {QThread.currentThread()}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 try:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if not index.isValid():\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return None\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 #logger.debug(f\"Entering role check for {file_path}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if role not in (Qt.DisplayRole, Qt.DecorationRole, Qt.ToolTipRole):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0#logger.debug(f\"Unsupported role {role} for {file_path}, returning None\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0return None\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 #logger.debug(f\"Processing role {role} for {file_path}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if role == Qt.DisplayRole:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 filename = os.path.basename(file_path)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 #logger.debug(f\"DisplayRole: Original filename {filename}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if file_path in QDir.drives():\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 filename = file_path\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 #logger.debug(f\"DisplayRole: Filename is a drive {filename}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 longueur_max_titre = 25\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if len(filename) > longueur_max_titre:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 filename = filename[:longueur_max_titre - 3] + \"...\"\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return filename\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 elif role == Qt.ToolTipRole:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 #logger.debug(f\"ToolTipRole: Returning file path {file_path}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return file_path\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 elif role == Qt.DecorationRole and index.column() == 0:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 #logger.debug(f\"CustomFileSystemModelForList: Standard data method called ; Requesting thumbnail for {file_path} , role: {role}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if os.path.isdir(file_path):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 #logger.debug(f\"DecorationRole: {file_path} is a directory, returning folder icon\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return self.folder_icon\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 elif file_path.lower().endswith(('.dxf', '.png', '.jpg', '.jpeg')):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 thumbnail = self.thumbnailManager.cache.get(file_path)\u00a0 # V\u00e9rifier le cache\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if thumbnail is None:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 with QMutexLocker(self.thumbnailManager.processing_thread.pending_requests_lock):\u00a0 # Utiliser le verrou sp\u00e9cifique\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # V\u00e9rifier les requ\u00eates en attente dans ThumbnailManager\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if file_path not in self.thumbnailManager.processing_thread.pending_requests:\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.thumbnailManager.processing_thread.pending_requests.add(file_path)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if QThread.currentThread() == QApplication.instance().thread():\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.thumbnailRequested.emit(file_path)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # \u00c9mettre le signal depuis le thread principal en utilisant un timer\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 QTimer.singleShot(0, lambda: self.thumbnailRequested.emit(file_path))\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return QPixmap()\u00a0 # Retourner une image vide en attendant\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return thumbnail.scaled(160, 120, Qt.KeepAspectRatio, Qt.SmoothTransformation)\u00a0 # Redimensionner la vignette\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return None\u00a0 # Aucun ic\u00f4ne pour les autres types de fichiers\n\n\u00a0 \u00a0 \u00a0 \u00a0 except Exception as e:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.exception(f\"CustomFileSystemModelForList.data: Exception occurred: {e}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return None\n\n\u00a0\n\n\u00a0 \u00a0 @pyqtSlot(str, QPixmap, bool)\n\n\u00a0 \u00a0 def on_thumbnail_ready(self, file_path, thumbnail, success):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \"\"\"Slot appel\u00e9 lorsque la vignette est pr\u00eate.\"\"\"\n\n\u00a0 \u00a0 \u00a0 \u00a0 #logger.debug(f\"CustomFileSystemModelForList: on_thumbnail_ready() - file_path: {file_path}, success: {success}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 if success:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.update_thumbnail_cache(file_path, thumbnail)\n\n\u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.warning(f\"on_thumbnail_ready: \u00c9chec de la g\u00e9n\u00e9ration de la vignette pour {file_path}\")\n\n\u00a0 \u00a0 @pyqtSlot(str, bool)\n\n\u00a0 \u00a0 def handle_request_processed(self, file_path, success):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \"\"\"G\u00e8re le signal requestProcessed du ProcessingThread.\"\"\"\n\n\u00a0 \u00a0 \u00a0 \u00a0 if not success:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # G\u00e9rez l'\u00e9chec de la g\u00e9n\u00e9ration de la vignette (r\u00e9essayez, affichez un message d'erreur, etc.)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(f\"CustomFileSystemModelForList: Failed to generate thumbnail for {file_path}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"CustomFileSystemModelForList: Thumbnail successfully generated for {file_path}\")\n\n\n\n\u00a0 \u00a0 @pyqtSlot(str, QPixmap)\n\n\u00a0 \u00a0 def update_thumbnail_cache(self, file_path, thumbnail):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \"\"\"Met \u00e0 jour le cache des vignettes et \u00e9met le signal dataChanged dans le thread principal.\"\"\"\n\n\u00a0 \u00a0 \u00a0 \u00a0 #logger.debug(f\"CustomFileSystemModelForList: update_thumbnail_cache() - Entr\u00e9e avec file_path: {file_path}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 # Utiliser QMutexLocker pour assurer le d\u00e9verrouillage automatique\n\n\u00a0 \u00a0 \u00a0 \u00a0 with QMutexLocker(self.thumbnailManager.lock):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 #logger.debug(f\"CustomFileSystemModelForList: update_thumbnail_cache() - Verrou acquis\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if file_path not in self.thumbnailManager.cache or self.thumbnailManager.cache[file_path] != thumbnail:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 #logger.debug(f\"CustomFileSystemModelForList: update_thumbnail_cache() - Vignette absente du cache ou diff\u00e9rente, mise \u00e0 jour en cours...\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.thumbnailManager.cache[file_path] = thumbnail\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 #logger.debug(f\"CustomFileSystemModelForList: update_thumbnail_cache() - Vignette mise \u00e0 jour dans le cache pour {file_path}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"CustomFileSystemModelForList: update_thumbnail_cache() - Vignette d\u00e9j\u00e0 pr\u00e9sente dans le cache, aucune mise \u00e0 jour n\u00e9cessaire.\")\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 # \u00c9mettre le signal dataChanged pour indiquer que les donn\u00e9es ont chang\u00e9\n\n\u00a0 \u00a0 \u00a0 \u00a0 index = self.indexFromPath(file_path)\n\n\u00a0 \u00a0 \u00a0 \u00a0 if index.isValid():\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 #logger.debug(f\"CustomFileSystemModelForList: update_thumbnail_cache() - \u00c9mission du signal dataChanged pour {file_path}\")\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.dataChanged.emit(index, index, [Qt.DecorationRole])\u00a0 # \u00c9mettre le signal dataChanged pour l'\u00e9l\u00e9ment sp\u00e9cifique\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 #logger.debug(f\"CustomFileSystemModelForList: update_thumbnail_cache() - Signal dataChanged \u00e9mis pour {file_path}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.warning(f\"CustomFileSystemModelForList: update_thumbnail_cache() - Index invalide pour {file_path}\")\n\n\n\nclass CustomFileSystemModelForTree(QFileSystemModel):\n\n\u00a0 \u00a0 def __init__(self, *args, **kwargs):\n\n\u00a0 \u00a0 \u00a0 \u00a0 super().__init__(*args, **kwargs)\n\n\u00a0 \u00a0 \u00a0 \u00a0 # Cache the folder icon for performance\n\n\u00a0 \u00a0 \u00a0 \u00a0 script_directory = os.path.dirname(os.path.abspath(__file__))\n\n\u00a0 \u00a0 \u00a0 \u00a0 icon_path = os.path.join(script_directory, \"dossier.png\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.folder_icon = QIcon(QPixmap(icon_path))\n\n\u00a0 \u00a0 \u00a0 \u00a0 if self.folder_icon.isNull():\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(f\"Failed to load folder icon from {icon_path}\")\n\n\n\n\u00a0 \u00a0 def data(self, index, role):\n\n\u00a0 \u00a0 \u00a0 \u00a0 if role == Qt.DecorationRole and index.column() == 0:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # Get the full path of the file or directory at the index\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 file_path = self.filePath(index)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # Check if it's a directory\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if os.path.isdir(file_path):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return self.folder_icon\n\n\u00a0 \u00a0 \u00a0 \u00a0 elif role == Qt.DisplayRole:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # Get the default display text\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 original_text = super().data(index, role)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return original_text\n\n\u00a0 \u00a0 \u00a0 \u00a0 return None\n\n\n\n# Background search worker class\n\nclass SearchWorker(QObject):\n\n\u00a0 \u00a0 searchFinished = pyqtSignal(bool, list)\u00a0 # Signal to indicate search success\n\n\n\n\u00a0 \u00a0 def __init__(self, indexer, index_dir):\n\n\u00a0 \u00a0 \u00a0 \u00a0 super().__init__()\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.running = False\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.running_mutex = QMutex()\u00a0 # Mutex to protect self.running\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.rootPath = \"\"\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.criteria = \"\"\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.criteria_type = \"filename\"\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.start_date = None\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.end_date = None\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.allowed_extensions = ALLOWED_EXTENSIONS\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.extension_pattern = re.compile(r\".*(\" + \"|\".join([re.escape(ext) + \"$\" for ext in self.allowed_extensions]) + \")\", re.IGNORECASE)\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.indexer = indexer\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.index_dir = os.path.normpath(index_dir)\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"SearchWorker initialized with index_dir: {self.index_dir}\")\n\n\n\n\u00a0 \u00a0 def stop(self):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \"\"\"Stop the ongoing search gracefully.\"\"\"\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.running_mutex.lock()\n\n\u00a0 \u00a0 \u00a0 \u00a0 running = self.running\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.running_mutex.unlock()\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 if not running:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"Search interrupted by user.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return\u00a0 # Exit if the search was not running\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"Search stopped by user.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.running = False\u00a0 # This will cause the search loop to exit\n\n\n\n\u00a0 \u00a0 def setCriteria(self, criteria, criteria_type=\"filename\"):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \"\"\"Sets the search criteria and its type.\"\"\"\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.criteria = criteria.lower()\u00a0 # Convert criteria to lowercase\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.criteria_type = criteria_type\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"Search criteria set: {criteria} (type: {criteria_type})\")\n\n\n\n\u00a0 \u00a0 def setBasePath(self, path):\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.rootPath = os.path.normpath(path)\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"Base path for search set: {self.rootPath}\")\n\n\n\n\u00a0 \u00a0 def setDateRange(self, start_date, end_date):\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.start_date = start_date.toPyDate()\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.end_date = end_date.toPyDate()\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"Date range set: {self.start_date} - {self.end_date}\")\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 # Pre-calculate timestamps for date comparison\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.start_date_timestamp = datetime.combine(self.start_date, datetime.min.time()).timestamp()\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.end_date_timestamp = datetime.combine(self.end_date, datetime.max.time()).timestamp()\n\n\n\n\u00a0 \u00a0 def performSearch(self, retry=False):\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"Starting search.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"Root path for search: {self.rootPath}\")\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 # Verify root path\n\n\u00a0 \u00a0 \u00a0 \u00a0 if not self.rootPath or not os.path.exists(self.rootPath):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(\"Base path not set or does not exist.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.searchFinished.emit(False, [])\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 # Validate dates\n\n\u00a0 \u00a0 \u00a0 \u00a0 if self.start_date and self.end_date and self.start_date > self.end_date:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(\"Start date must be before end date.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.searchFinished.emit(False, [])\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.running_mutex.lock()\u00a0 # Lock before modifying\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.running = True\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.running_mutex.unlock()\u00a0 # Unlock after modification\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 results = []\n\n\u00a0 \u00a0 \u00a0 \u00a0 criteria_lower = self.criteria.lower().strip()\n\n\u00a0 \u00a0 \u00a0 \u00a0 if not criteria_lower:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(\"Search criteria is empty after processing. Aborting search.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.searchFinished.emit(False, [])\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 try:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # Check if within watched directory\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 normalized_root_path = os.path.normcase(os.path.normpath(self.rootPath))\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 normalized_watched_dir = os.path.normcase(os.path.normpath(WATCHED_DIR))\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if os.path.commonpath([normalized_root_path, normalized_watched_dir]) == normalized_watched_dir:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"Using index-based search.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.index_based_search(results, criteria_lower, retry)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"Using file system search.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.scan_directory(normalized_root_path, results, criteria_lower)\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 #logger.debug(f\"Final search results: {results}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 except Exception as e:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(f\"Error during search: {e}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.searchFinished.emit(False, [])\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return\n\n\u00a0 \u00a0 \u00a0 \u00a0 finally:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"Finalizing search.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.running_mutex.lock()\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.running = False\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.running_mutex.unlock()\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.searchFinished.emit(True, results)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.info(f\"Search completed with {len(results)} results.\")\n\n\n\n\u00a0 \u00a0 def index_based_search(self, results, criteria_lower, retry):\n\n\u00a0 \u00a0 \u00a0 \u00a0 try:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"Performing index-based search.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 ix = open_dir(self.index_dir)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 with ix.searcher() as searcher:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 query_parts = []\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # Build query based on criteria type\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if self.criteria_type == \"filename\":\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if not criteria_lower.strip():\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 raise ValueError(\"Search criteria for filename is empty.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 query_part = Prefix(\"name\", criteria_lower)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 query_parts.append(query_part)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 elif self.criteria_type in (\"creation_date\", \"modification_date\"):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 date_field = \"creation_time\" if self.criteria_type == \"creation_date\" else \"modification_time\"\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 query_parts.append(DateRange(date_field, self.start_date, self.end_date))\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # Filter by extensions using regex\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if self.allowed_extensions:\u00a0 # Check if extensions are specified\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 query_parts.append(self.extension_pattern)\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # Filter by path\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 query_parts.append(Prefix(\"path\", os.path.normcase(os.path.normpath(self.rootPath))))\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 query = And(query_parts)\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # Execute the search\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 hits = searcher.search(query, limit=None)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 for hit in hits:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.running_mutex.lock()\u00a0 # Protect access to self.running\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 running = self.running\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.running_mutex.unlock()\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if not running:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 break\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 results.append(hit[\"path\"])\n\n\u00a0 \u00a0 \u00a0 \u00a0 except EmptyIndexError:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if not retry:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.warning(\"Index directory is empty, creating index...\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.indexer.buildIndex()\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return self.performSearch(retry=True)\u00a0 # Retry after rebuilding index\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(\"Index is still empty after rebuilding.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.searchFinished.emit(False, [])\n\n\u00a0 \u00a0 \u00a0 \u00a0 except Exception as e:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(f\"Error during Whoosh search: {e}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.searchFinished.emit(False, [])\n\n\n\n\u00a0 \u00a0 def scan_directory(self, directory, results, criteria_lower):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \"\"\"Recursively scan the directory for files matching the criteria.\"\"\"\n\n\u00a0 \u00a0 \u00a0 \u00a0 for entry in os.scandir(directory):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if entry.is_dir():\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.scan_directory(entry.path, results, criteria_lower)\u00a0 # Recursive call for subdirectories\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 elif entry.is_file():\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.process_file(entry.path, entry.name, results, criteria_lower)\n\n\n\n\u00a0 \u00a0 def process_file(self, fullPath, file, results, criteria_lower):\n\n\u00a0 \u00a0 \u00a0 \u00a0 try:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 file_lower = file.lower()\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 _, extension = os.path.splitext(fullPath)\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # Check extension using regex\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if not self.extension_pattern.match(fullPath):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # Check criteria\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if self.criteria_type == \"filename\" and criteria_lower in file_lower:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 results.append(fullPath)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 elif self.criteria_type in (\"creation_date\", \"modification_date\"):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 date_to_check = os.path.getctime(fullPath) if self.criteria_type == \"creation_date\" else os.path.getmtime(fullPath)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if self.compare_dates(date_to_check) :\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if criteria_lower in file_lower:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 results.append(fullPath)\n\n\u00a0 \u00a0 \u00a0 \u00a0 except Exception as e:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(f\"Error processing file {fullPath}: {e}\")\n\n\n\n\u00a0 \u00a0 def compare_dates(self, file_timestamp):\n\n\u00a0 \u00a0 \u00a0 \u00a0 return self.start_date_timestamp <= file_timestamp <= self.end_date_timestamp\n\nclass TreeProxyModel(QSortFilterProxyModel):\n\n\u00a0 \u00a0 def filterAcceptsRow(self, source_row, source_parent):\n\n\u00a0 \u00a0 \u00a0 \u00a0 index = self.sourceModel().index(source_row, 0, source_parent)\n\n\u00a0 \u00a0 \u00a0 \u00a0 if self.sourceModel().isDir(index):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 #logger.debug(f\"Folder accepted for display: {index.data()}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return True\u00a0 # Accept the entry as it is a folder\n\n\u00a0 \u00a0 \u00a0 \u00a0 #logger.debug(f\"Entry rejected (not a folder): {index.data()}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 return False\u00a0 # Reject the entry as it is not a folder\n\n\n\nclass FileListProxyModel(QSortFilterProxyModel):\n\n\u00a0 \u00a0 def __init__(self, parent=None):\n\n\u00a0 \u00a0 \u00a0 \u00a0 super(FileListProxyModel, self).__init__(parent)\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.filter_text = \"\"\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.filters = ['.dxf', '.png', '.jpg', '.jpeg']\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.recursiveSearchEnabled = True\u00a0 # Enabled by default\u00a0 \u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.searchPaths = []\u00a0 # Paths to use for filtering results\u00a0 \u00a0 \u00a0\n\n\n\n\u00a0 \u00a0 def setFilterText(self, text):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \"\"\"Set the filter text and invalidate the current filter.\"\"\"\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.filter_text = text.lower()\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.invalidateFilter()\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"Filter text set: {self.filter_text}\")\u00a0 \u00a0\n\n\n\n\u00a0 \u00a0 def setSearchPaths(self, paths):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \"\"\"Set the search paths for filtering.\"\"\"\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.searchPaths = paths\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.invalidateFilter()\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"Search paths set: {self.searchPaths}\")\u00a0\n\n\n\n\u00a0 \u00a0 def setRecursiveSearchEnabled(self, enabled):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \"\"\"Enable or disable recursive search.\"\"\"\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.recursiveSearchEnabled = enabled\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.invalidateFilter()\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"Recursive search {'enabled' if enabled else 'disabled'}.\")\n\n\u00a0 \u00a0\u00a0\n\n\u00a0 \u00a0 def filterAcceptsRow(self, sourceRow, sourceParent):\n\n\u00a0 \u00a0 \u00a0 \u00a0 sourceModel = self.sourceModel()\n\n\u00a0 \u00a0 \u00a0 \u00a0 if not sourceModel:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return False\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 sourceIndex = sourceModel.index(sourceRow, 0, sourceParent)\n\n\u00a0 \u00a0 \u00a0 \u00a0 if not sourceIndex.isValid():\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return False\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 # Standard filtering logic\n\n\u00a0 \u00a0 \u00a0 \u00a0 filepath = sourceModel.filePath(sourceIndex).lower()\n\n\u00a0 \u00a0 \u00a0 \u00a0 isDirectory = sourceModel.isDir(sourceIndex)\n\n\u00a0 \u00a0 \u00a0 \u00a0 isAcceptableFile = isDirectory or any(filepath.endswith(ext) for ext in self.filters)\n\n\u00a0 \u00a0 \u00a0 \u00a0 filename = sourceModel.fileName(sourceIndex).lower()\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 if self.searchPaths:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if not any(filepath.startswith(path.lower()) for path in self.searchPaths):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 #logger.debug(f\"Path {filepath} rejected by search paths.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return False\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 if self.filter_text:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 filename = self.sourceModel().data(sourceIndex, Qt.DisplayRole).lower()\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 textMatch = self.filter_text in filename or self.filter_text in filepath\n\n\u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 textMatch = True\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 # Final decision for row acceptance\n\n\u00a0 \u00a0 \u00a0 \u00a0 if isAcceptableFile and textMatch:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return True\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 # If recursive search is enabled and the index is a directory, check subdirectories\n\n\u00a0 \u00a0 \u00a0 \u00a0 if self.recursiveSearchEnabled and isDirectory:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 rowCount = sourceModel.rowCount(sourceIndex)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"Recursive search enabled, number of subdirectories for {filepath}: {rowCount}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 for childRow in range(rowCount):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if self.filterAcceptsRow(childRow, sourceIndex):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"Recursive acceptance for {filepath} due to subdirectory at row {childRow}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return True\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 #logger.debug(f\"Rejected {filepath}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 return False\n\n\n\nclass SearchResultsProxyModel(QAbstractProxyModel):\n\n\u00a0 \u00a0 def __init__(self, parent=None):\n\n\u00a0 \u00a0 \u00a0 \u00a0 super().__init__(parent)\n\n\u00a0 \u00a0 \u00a0 \u00a0 self._search_results = []\u00a0 # Stocke les chemins des fichiers trouv\u00e9s\n\n\u00a0 \u00a0 \u00a0 \u00a0 self._path_to_row = {}\u00a0 \u00a0 \u00a0# Mapping from path to row index\n\n\u00a0 \u00a0 \u00a0 \u00a0 self._cached_source_indexes = {}\u00a0 # Cache pour les index source\n\n\n\n\u00a0 \u00a0 def setSearchResults(self, results):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \"\"\"Met \u00e0 jour les r\u00e9sultats de la recherche et signale les changements au mod\u00e8le.\"\"\"\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"SearchResultsProxyModel.setSearchResults called with {len(results)} results.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.beginResetModel()\n\n\u00a0 \u00a0 \u00a0 \u00a0 self._search_results = [os.path.normcase(r) for r in results]\n\n\u00a0 \u00a0 \u00a0 \u00a0 self._path_to_row = {path: idx for idx, path in enumerate(self._search_results)}\n\n\u00a0 \u00a0 \u00a0 \u00a0 self._cached_source_indexes.clear()\u00a0 # Vider le cache car les r\u00e9sultats ont chang\u00e9\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.endResetModel()\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"setSearchResults: Model reset completed.\")\u00a0\n\n\n\n\u00a0 \u00a0 def rowCount(self, parent=QModelIndex()):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \"\"\"Retourne le nombre de r\u00e9sultats de recherche.\"\"\"\n\n\u00a0 \u00a0 \u00a0 \u00a0 if parent.isValid():\u00a0 # Si l'index parent est valide, on est dans un noeud enfant, donc 0 enfant\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return 0\n\n\u00a0 \u00a0 \u00a0 \u00a0 return len(self._search_results)\n\n\n\n\u00a0 \u00a0 def filePath(self, index):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \"\"\"Retourne le chemin du fichier pour l'index donn\u00e9, en d\u00e9l\u00e9guant au mod\u00e8le source.\"\"\"\n\n\u00a0 \u00a0 \u00a0 \u00a0 source_index = self.mapToSource(index)\n\n\u00a0 \u00a0 \u00a0 \u00a0 return self.sourceModel().filePath(source_index)\n\n\n\n\u00a0 \u00a0 def columnCount(self, parent=QModelIndex()):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \"\"\"Retourne le nombre de colonnes du mod\u00e8le source.\"\"\"\n\n\u00a0 \u00a0 \u00a0 \u00a0 return self.sourceModel().columnCount(parent) if self.sourceModel() else 0\n\n\n\n\u00a0 \u00a0 def index(self, row, column, parent=QModelIndex()):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \"\"\"Retourne l'index du proxy pour la ligne, la colonne et le parent donn\u00e9s.\"\"\"\n\n\u00a0 \u00a0 \u00a0 \u00a0 if not parent.isValid() and 0 <= row < len(self._search_results):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 source_index = self.sourceModel().indexFromPath(self._search_results[row])\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if source_index.isValid():\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return self.createIndex(row, column, source_index)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(f\"Invalid source index for path: {self._search_results[row]}\")\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 return QModelIndex()\n\n\n\n\u00a0 \u00a0 def parent(self, index):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \"\"\"Retourne l'index parent, toujours invalide pour ce mod\u00e8le.\"\"\"\n\n\u00a0 \u00a0 \u00a0 \u00a0 return QModelIndex()\n\n\n\n\u00a0 \u00a0 def mapFromSource(self, sourceIndex):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \"\"\"Retourne l'index du proxy correspondant \u00e0 l'index source.\"\"\"\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"SearchResultsProxyModel.mapFromSource appel\u00e9 avec sourceIndex: {sourceIndex}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 if sourceIndex.isValid():\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 source_file_path = os.path.normcase(os.path.normpath(self.sourceModel().filePath(sourceIndex)))\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"Source File Path: {source_file_path}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 row = self._path_to_row.get(source_file_path)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"Mapped Row: {row}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if row is not None:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return self.createIndex(row, sourceIndex.column())\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.warning(\"SearchResultsProxyModel.mapFromSource re\u00e7u un sourceIndex invalide.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return QModelIndex()\n\n\u00a0 \u00a0 \u00a0 \u00a0 return QModelIndex()\n\n\n\n\u00a0 \u00a0 def mapToSource(self, proxyIndex):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \"\"\"Retourne l'index source correspondant \u00e0 l'index du proxy.\"\"\"\n\n\u00a0 \u00a0 \u00a0 \u00a0 #logger.debug(f\"FileListProxyModel.mapToSource appel\u00e9 avec proxyIndex: {proxyIndex}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 if proxyIndex.isValid() and 0 <= proxyIndex.row() < len(self._search_results):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 source_path = self._search_results[proxyIndex.row()]\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if source_path in self._cached_source_indexes:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return self._cached_source_indexes[source_path]\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 source_index = self.sourceModel().indexFromPath(source_path)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if source_index.isValid():\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self._cached_source_indexes[source_path] = source_index\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return source_index\n\n\u00a0 \u00a0 \u00a0 \u00a0 return QModelIndex()\n\n\u00a0 \u00a0\u00a0\n\n\u00a0 \u00a0 def data(self, index, role=Qt.DisplayRole):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \"\"\"Retourne les donn\u00e9es pour l'index donn\u00e9 et le r\u00f4le.\"\"\"\n\n\u00a0 \u00a0 \u00a0 \u00a0 if not index.isValid():\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Invalid index in data method.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return None\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 if not self.sourceModel():\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(\"Source model is not set.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return None\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 source_index = self.mapToSource(index)\u00a0 # Obtenir un nouvel index source valide\n\n\u00a0 \u00a0 \u00a0 \u00a0 if not source_index.isValid():\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Invalid source index in data method.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return None\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 return self.sourceModel().data(source_index, role)\n\n\u00a0 \u00a0 def setSourceModel(self, source_model):\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"SearchResultsProxyModel: setSourceModel called\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 super().setSourceModel(source_model)\n\n\u00a0 \u00a0 \u00a0 \u00a0 if source_model is not None:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 source_model.dataChanged.connect(self.sourceDataChanged)\n\n\n\n\u00a0 \u00a0 def sourceDataChanged(self, sourceTopLeft, sourceBottomRight, roles):\n\n\u00a0 \u00a0 \u00a0 \u00a0 #logger.debug(f\"SearchResultsProxyModel: sourceDataChanged called with sourceTopLeft={sourceTopLeft}, sourceBottomRight={sourceBottomRight}, roles={roles}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 # Mapper les index source vers les index du proxy\n\n\u00a0 \u00a0 \u00a0 \u00a0 proxyTopLeft = self.mapFromSource(sourceTopLeft)\n\n\u00a0 \u00a0 \u00a0 \u00a0 proxyBottomRight = self.mapFromSource(sourceBottomRight)\n\n\u00a0 \u00a0 \u00a0 \u00a0 #logger.debug(f\"SearchResultsProxyModel: Mapped proxyTopLeft={proxyTopLeft}, proxyBottomRight={proxyBottomRight}\")\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 if proxyTopLeft.isValid() and proxyBottomRight.isValid():\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 #logger.debug(\"SearchResultsProxyModel: Emitting dataChanged from proxy model\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # \u00c9mettre le signal dataChanged du proxy avec les r\u00f4les appropri\u00e9s\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.dataChanged.emit(proxyTopLeft, proxyBottomRight, roles)\n\n\u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"SearchResultsProxyModel: Skipping dataChanged emission, model is not active.\")\n\n\u00a0 \u00a0\u00a0\n\nclass ThumbnailWorkerSignals(QObject):\n\n\u00a0 \u00a0 \"\"\"Classe pour regrouper les signaux du ThumbnailWorker.\"\"\"\n\n\u00a0 \u00a0 thumbnailReady = pyqtSignal(str, QPixmap, bool)\u00a0 # Chemin du fichier, vignette, succ\u00e8s\n\n\u00a0 \u00a0 finished = pyqtSignal()\n\n\u00a0 \u00a0 errorOccurred = pyqtSignal(str, Exception)\u00a0 # Chemin du fichier, exception\n\n\u00a0\n\nclass ThumbnailWorker(QRunnable):\n\n\u00a0 \u00a0 \"\"\"Worker pour g\u00e9n\u00e9rer les vignettes de mani\u00e8re asynchrone.\"\"\"\n\n\n\n\u00a0 \u00a0 def __init__(self, file_path, cache):\n\n\u00a0 \u00a0 \u00a0 \u00a0 super().__init__()\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.signals = ThumbnailWorkerSignals()\u00a0 # Instance pour g\u00e9rer les signaux\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.file_path = file_path\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.cache = cache\u00a0 # R\u00e9f\u00e9rence au cache partag\u00e9\n\n\u00a0 \u00a0 \u00a0 \u00a0 self._cancel_requested = False\n\n\n\n\u00a0 \u00a0 @pyqtSlot()\n\n\u00a0 \u00a0 def cancel(self):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \"\"\"Annule la g\u00e9n\u00e9ration de la vignette si elle est en cours.\"\"\"\n\n\u00a0 \u00a0 \u00a0 \u00a0 self._cancel_requested = True\n\n\n\n\u00a0 \u00a0 @pyqtSlot()\u00a0 # Supprimer l'argument file_path\n\n\u00a0 \u00a0 def run(self):\u00a0 # M\u00e9thode ex\u00e9cut\u00e9e dans un thread s\u00e9par\u00e9 pour g\u00e9n\u00e9rer la vignette\n\n\u00a0 \u00a0 \u00a0 \u00a0 #logger.debug(f\"ThumbnailWorker: run() D\u00e9marrage pour {self.file_path}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 if self._cancel_requested:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.warning(f\"ThumbnailWorker: run() cancelled for {self.file_path}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 # V\u00e9rifier si la vignette est d\u00e9j\u00e0 dans le cache\n\n\u00a0 \u00a0 \u00a0 \u00a0 if self.file_path in self.cache:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 #logger.debug(f\"ThumbnailWorker: Thumbnail found in cache for {self.file_path}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.signals.thumbnailReady.emit(self.file_path, self.cache[self.file_path], True)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.signals.finished.emit()\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return\n\n\u00a0 \u00a0 \u00a0 \u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 try:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 #logger.debug(f\"ThumbnailWorker: Generating thumbnail for {self.file_path}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 thumbnail = self.generate_thumbnail(self.file_path)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 success = thumbnail is not None and not thumbnail.isNull()\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 #logger.debug(f\"ThumbnailWorker: Thumbnail generated for {self.file_path}, success: {success}\")\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if success:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.signals.thumbnailReady.emit(self.file_path, thumbnail, success)\n\n\u00a0 \u00a0 \u00a0 \u00a0 except (FileNotFoundError, ValueError, CancelledError) as e:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(f\"ThumbnailWorker: Error generating thumbnail for {self.file_path}: {e}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.signals.errorOccurred.emit(self.file_path, e)\n\n\u00a0 \u00a0 \u00a0 \u00a0 finally:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.signals.finished.emit()\n\n\n\n\u00a0 \u00a0 def generate_thumbnail(self, file_path):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \"\"\"G\u00e9n\u00e8re une vignette pour le fichier donn\u00e9.\"\"\"\n\n\u00a0 \u00a0 \u00a0 \u00a0 #logger.debug(f\"ThumbnailWorker: generate_thumbnail called for {file_path}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 try:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if file_path.lower().endswith(('.png', '.jpg', '.jpeg')):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 thumbnail = self.generate_image_thumbnail(file_path)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 elif file_path.lower().endswith('.dxf'):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 thumbnail = self.generate_dxf_thumbnail(file_path)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.warning(f\"ThumbnailWorker: generate_thumbnail() - Unsupported file format for {file_path}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 raise ValueError(f\"Unsupported file format: {file_path}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return thumbnail\n\n\u00a0 \u00a0 \u00a0 \u00a0 except Exception as e:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(f\"ThumbnailWorker: Error generating thumbnail for {file_path}: {e}\", exc_info=True)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 raise\n\n\n\n\u00a0 \u00a0 def generate_image_thumbnail(self, image_path):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \"\"\"G\u00e9n\u00e8re une miniature \u00e0 partir d'un fichier image.\"\"\"\n\n\u00a0 \u00a0 \u00a0 \u00a0 #logger.debug(f\"ThumbnailWorker: generate_image_thumbnail for image: {image_path}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 try:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if self._cancel_requested:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.warning(f\"ThumbnailWorker: generate_image_thumbnail - Annul\u00e9 pour {image_path}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return None\u00a0 # Return None if cancelle\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 size = QSize(160, 120)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 reader = QImageReader(image_path)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 reader.setAutoTransform(True)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 image = reader.read()\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if image.isNull():\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(f\"Impossible de lire l'image: {image_path}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return QPixmap()\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 thumbnail = QPixmap.fromImage(image).scaled(size, Qt.KeepAspectRatio, Qt.SmoothTransformation)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return thumbnail\n\n\u00a0 \u00a0 \u00a0 \u00a0 except ValueError as ve:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(f\"ValueError creating thumbnail for {image_path}: {ve}\", exc_info=True)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return QPixmap()\n\n\u00a0 \u00a0 \u00a0 \u00a0 except Exception as e:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(f\"Error creating thumbnail for {image_path}: {e}\", exc_info=True)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return QPixmap()\u00a0 # Return an empty QPixmap on failure\n\n\u00a0#Suite de la class...\n\n\n\nclass FileViewer(QMainWindow):\n\n\u00a0 \u00a0 folderChanged\u00a0 = pyqtSignal(str)\n\n\u00a0 \u00a0 fileSelected = pyqtSignal(str)\n\n\u00a0 \u00a0 rechercheChanged = pyqtSignal(str)\n\n\u00a0 \u00a0 def __init__(self):\n\n\u00a0 \u00a0 \u00a0 \u00a0 super().__init__()\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.scale_factor = 1\u00a0 # Initialiser avec une valeur par d\u00e9faut\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.indexeur = FileIndexer()\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.setWindowTitle(\"Souf Dxf Explorer\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.setGeometry(100, 100, 800, 600)\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.model = CustomFileSystemModelForList(self)\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.isSearchThreadRunning = False\n\n\u00a0 \u00a0 \u00a0 \u00a0 self._in_search_mode = False\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 # Search indicator\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.search_indicator = QLabel(\"Recherche en cours...\", self)\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.statusBar().addWidget(self.search_indicator)\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.search_indicator.hide()\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.current_folder = os.path.normpath(os.path.expanduser(\"~\"))\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.previous_folder = None\n\n\u00a0 \u00a0 \u00a0 \u00a0 #self.current_folders = [QDir.homePath()]\u00a0 # Initialize the current_folders attribute\n\n\u00a0 \u00a0 \u00a0 \u00a0 #self.rootPaths = [\"Y:/\"]\n\n\u00a0 \u00a0 \u00a0 \u00a0 #logger.debug(f\"[Init] Current folders initialized to: {self.current_folders}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.setupModelsAndView()\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.setupUIComponents()\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.setupObservers()\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.connect_signals()\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.indexeur.indexUpdated.connect(self.on_indexing_finished)\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.indexeur.indexerError.connect(self.on_indexing_error) # connecter au slot\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.indexeur.indexingProgress.connect(self.update_indexing_progress)\u00a0 # Connecter le signal\n\n\u00a0 \u00a0 \u00a0 \u00a0 # D\u00e9marrer l'indexation asynchrone apr\u00e8s l'initialisation de l'interface\n\n\u00a0 \u00a0 \u00a0 \u00a0 #QTimer.singleShot(3000, lambda: (self.indexeur.buildIndexAsync(), logger.info(\" Fileviewer.__init__(self) (self.indexeur.buildIndexAsync())Lambda function in QTimer.singleShot executed\")))\n\n\u00a0 \u00a0 \u00a0 \u00a0 #logger.info(\"FileViewer / Asynchronous indexing initiated.\")\u00a0\n\n\u00a0 \u00a0\u00a0\n\n\u00a0 \u00a0 def connect_signals(self):\n\n\u00a0 \u00a0 \u00a0 \u00a0 if not hasattr(self, 'signals_connected'):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # Connexions pour la mise \u00e0 jour de la vue de la liste de fichiers\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.file_list.model().dataChanged.connect(self.update_file_list_view)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.model.modelUpdated.connect(self.update_file_list_view)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"Connection: file_list.clicked -> display_selected_file\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.file_list.clicked.connect(self.display_selected_file)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"Connection: file_list.doubleClicked -> on_folder_double_clicked\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.file_list.doubleClicked.connect(self.on_folder_double_clicked)\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # Connexions pour la recherche\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.SearchWorker.searchFinished.connect(self.onSearchFinished)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.searchThread.started.connect(self.SearchWorker.performSearch)\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # Connexions pour les temporisateurs et les champs de texte\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.search_timer.timeout.connect(self.launchSearch)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.search_box.textChanged.connect(self.launch_delayed_search)\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # Configuration du proxy de recherche\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.search_results_proxy_model = SearchResultsProxyModel(self)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.search_results_proxy_model.setSourceModel(self.model)\u00a0 # D\u00e9finir le mod\u00e8le source du proxy\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.signals_connected = True\n\n\n\n\u00a0 \u00a0 def setupUIComponents(self):\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.pathLabel = QLabel(\"Path:\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.setup_ui()\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.setupMenu()\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.setupToolbar()\n\n\n\n\u00a0 \u00a0 def setupModelsAndView(self):\n\n\u00a0 \u00a0 \u00a0 \u00a0 #self.indexeur = FileIndexer()\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.rootPaths = [os.path.normpath(\"\")]\n\n\u00a0 \u00a0 \u00a0 \u00a0 for path in self.rootPaths:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if os.path.exists(path):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 print(f\"Le chemin {path} est valide.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 #print(f\"Le chemin {path} n'est pas valide.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 print(f\"Le chemin path est:{path} \")\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.tree_model = CustomFileSystemModelForTree()\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.file_list_model = CustomFileSystemModelForList(self, self.indexeur)\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.file_list = QListView()\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.setup_tree_dock_widget()\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.setup_proxy_tree_model()\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 # Add SearchResultsProxyModel\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.search_results_proxy_model = SearchResultsProxyModel(self)\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.search_results_proxy_model.setSourceModel(self.file_list_model)\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 # Connect signal modelReset\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.search_results_proxy_model.modelReset.connect(self.file_list.reset)\n\n\u00a0 \u00a0 \u00a0 \u00a0 # Connect signals layoutChanged and dataChanged\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.search_results_proxy_model.layoutChanged.connect(self.file_list.reset)\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.search_results_proxy_model.dataChanged.connect(self.file_list.reset)\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 # Configure FileListProxyModel to use SearchResultsProxyModel as source\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.file_list_proxy = FileListProxyModel()\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.setup_file_list_proxy()\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.file_list_proxy.setSourceModel(self.file_list_model)\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 # Configure list view to use the proxy file_list_proxy\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.file_list.setModel(self.file_list_proxy)\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.file_list_model.directoryLoaded.connect(self.file_list_model.on_directory_loaded)\n\n\n\n\u00a0 \u00a0 def setupObservers(self):\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.SearchWorker = SearchWorker(self.indexeur, self.indexeur.index_dir)\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.searchThread = QThread()\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.SearchWorker.moveToThread(self.searchThread)\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.FileMonitor = FileMonitor(self.indexeur, self.SearchWorker.allowed_extensions)\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.observer = Observer()\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.observer.schedule(self.FileMonitor, path=os.path.normpath(WATCHED_DIR), recursive=True)\u00a0 \u00a0# Surveiller WATCHED_DIR\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.observer.start()\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.search_timer = QTimer(self)\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.search_timer.setSingleShot(True)\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.update_directory_label(f\"Current folder path: {self.tree_model.rootPath()}\")\n\n\n\n\u00a0 \u00a0 def displayResults(self, success, results):\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Beginning of displayResults function.\")\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 # Arr\u00eater le profilage et afficher les r\u00e9sultats\n\n\u00a0 \u00a0 \u00a0 \u00a0 #if hasattr(self, 'profiler'):\u00a0 # V\u00e9rifier si le profilage a \u00e9t\u00e9 activ\u00e9\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 #self.profiler.disable()\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 #s = io.StringIO()\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 #ps = pstats.Stats(self.profiler, stream=s).sort_stats('cumulative')\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 #ps.print_stats()\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0# logger.debug(f\"Profiling results:\\n{s.getvalue()}\")\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 # Eviter les appels r\u00e9cursifs ou redondants\n\n\u00a0 \u00a0 \u00a0 \u00a0 if hasattr(self, 'display_results_called') and self.display_results_called:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"displayResults already called, skipping.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.display_results_called = True\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.isSearchThreadRunning = False\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.search_indicator.hide()\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 # G\u00e9rer les diff\u00e9rents cas de figure\n\n\u00a0 \u00a0 \u00a0 \u00a0 if not success:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Handling search failure.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.handleSearchFailure()\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self._in_search_mode = False\u00a0 # Assurez de quitter le mode recherche en cas d'\u00e9chec\n\n\u00a0 \u00a0 \u00a0 \u00a0 elif not results:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Handling no results.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.handleNoResults()\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self._in_search_mode = False\n\n\u00a0 \u00a0 \u00a0 \u00a0 elif not self.search_box.text().strip():\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Search box is empty, resetting list view.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.resetListView()\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self._in_search_mode = False\n\n\u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # Afficher les r\u00e9sultats de la recherche\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self._in_search_mode = True\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Updating proxy model with search results.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.search_results_proxy_model.setSearchResults(results)\u00a0 # Mettre \u00e0 jour le proxy\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # Si le mod\u00e8le actuel de la vue n'est pas le mod\u00e8le de r\u00e9sultats, le d\u00e9finir\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if self.file_list.model() != self.search_results_proxy_model:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.file_list.setModel(self.search_results_proxy_model)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"SearchResultsProxyModel set as the current model for file_list.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # Rafra\u00eechir la vue\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 #self.file_list.reset()\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 #logger.debug(f\"displayResults: ListView reset. Number of items in view: {self.file_list.model().rowCount()} \")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # Forcer le focus et mettre \u00e0 jour la vue\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 #self.file_list.setFocus()\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 #self.file_list.update()\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 #self.file_list.repaint()\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # (Facultatif) S\u00e9lectionner le premier \u00e9l\u00e9ment si souhait\u00e9\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if results:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 first_result_index = self.search_results_proxy_model.index(0, 0)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if first_result_index.isValid():\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.file_list.setCurrentIndex(first_result_index)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Logging search results.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.logSearchResults(results)\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.display_results_called = False\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"End of displayResults function.\")\n\n\n\n\u00a0 \u00a0 def launch_delayed_search(self):\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Slot activated: launch_delayed_search\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 search_criteria = self.search_box.text().strip()\n\n\u00a0 \u00a0 \u00a0 \u00a0 if search_criteria:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"Search criteria modified. Waiting before launching search...\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if not self.previous_folder:\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.previous_folder = self.current_folder\u00a0 # Sauvegarder le dossier actuel\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.search_timer.stop()\u00a0 # Stop the existing timer before restarting\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if self.search_timer.isActive():\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.search_timer.stop()\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.search_timer.start(1000)\u00a0 # Delay of 1000 ms\n\n\u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"Search bar empty. Displaying current folder.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.exit_search_mode()\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"End of launch_delayed_search\")\n\n\u00a0 \u00a0 def launchSearch(self):\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Function launchSearch start\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 criteria = self.search_box.text().strip()\n\n\u00a0 \u00a0 \u00a0 \u00a0 criteria_type = self.search_criteria_type.currentText().lower().replace(\" \", \"_\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"Search criteria: {criteria}, criteria_type: {criteria_type}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"Current root path: {self.file_list_model.rootPath()}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"WATCHED_DIR: {WATCHED_DIR}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 if not criteria and criteria_type == \"filename\":\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Search criteria is empty, exiting search.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 #QMessageBox.warning(self, \"Warning\", \"Search criteria cannot be empty for filename search.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.exit_search_mode()\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 if self.isSearchThreadRunning:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"Search already running, stopping current search.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.SearchWorker.running = False\u00a0 # Stop the thread gracefully\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.searchThread.quit()\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.searchThread.wait()\n\n\u00a0 \u00a0 \u00a0 \u00a0 # Sauvegarder le dossier actuel avant de lancer la recherche\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 if not self.previous_folder:\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.previous_folder = self.current_folder\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"Saved current folder to previous_folder: {self.previous_folder}\")\u00a0\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.isSearchThreadRunning = True\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.SearchWorker.setCriteria(criteria, criteria_type)\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.SearchWorker.setDateRange(self.start_date.date(), self.end_date.date())\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 # Assurez-vous que rootPath est bien d\u00e9fini\n\n\u00a0 \u00a0 \u00a0 \u00a0 current_root_path = self.file_list_model.rootPath()\n\n\u00a0 \u00a0 \u00a0 \u00a0 if current_root_path:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.SearchWorker.setBasePath(current_root_path)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 #self.profiler = cProfile.Profile()\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 #self.profiler.enable()\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 #self.searchThread.started.connect(self.SearchWorker.performSearch)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.searchThread.start()\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.search_indicator.show()\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Function launchSearch end\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(\"Current root path is not set. Cannot start search.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.isSearchThreadRunning = False\n\n\u00a0 \u00a0 def onSearchFinished(self, success, results):\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Search finished with success: {}\".format(success))\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.isSearchThreadRunning = False\n\n\u00a0 \u00a0 \u00a0 \u00a0 if self.searchThread.isRunning():\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Stopping search thread.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.searchThread.quit()\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.searchThread.wait()\u00a0 # Ensure the thread is fully stopped\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Search thread stopped.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Search thread stopped and resources cleaned up.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.handle_search_completion(success, results)\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"End of onSearchFinished function.\")\n\n\n\n\u00a0 \u00a0 def handle_search_completion(self, success, results):\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Function handle_search_completion start\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 if success:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"Search completed successfully.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.displayResults(success, results)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # Connecter le proxy de recherche \u00e0 la vue SI en mode recherche\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if self._in_search_mode:\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.file_list.setModel(self.search_results_proxy_model)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # Revenir au proxy normal si on n'est plus en recherche\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.file_list.setModel(self.file_list_proxy)\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(\"Search failed, attempting to restart.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"Search results (should be empty on failure): {results}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # Resetting the thread to allow for new searches\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.searchThread.quit()\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.searchThread.wait()\u00a0 # Wait until the thread cleanly finishes\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.searchThread = QThread()\u00a0 # Recreate the thread for future use\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.SearchWorker = SearchWorker(self.indexeur, self.indexeur.index_dir)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.SearchWorker.moveToThread(self.searchThread)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.searchThread.started.connect(self.SearchWorker.performSearch)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.SearchWorker.searchFinished.connect(self.onSearchFinished)\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Function handle_search_completion end\")\n\n\n\n\u00a0 \u00a0 def handleSearchFailure(self):\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Beginning of handleSearchFailure function.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 QMessageBox.warning(self, \"Search Failed\", \"The search failed or was stopped without results.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.resetListView()\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"End of handleSearchFailure function.\")\n\n\n\n\u00a0 \u00a0 def handleNoResults(self):\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Beginning of handleNoResults function.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 QMessageBox.information(self, \"No Results\", \"No results to display.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.resetListView()\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"End of handleNoResults function.\")\n\n\n\n\u00a0 \u00a0 def logSearchResults(self, results):\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Beginning of logSearchResults function.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 for result in results[:min(5, len(results))]:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"Search result: {result}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"End of logSearchResults function.\")\n\n\u00a0 \u00a0\u00a0\n\n\u00a0 \u00a0 def exit_search_mode(self):\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Function exit_search_mode start\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 self._in_search_mode = False\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.file_list_proxy.invalidateFilter()\n\n\u00a0 \u00a0 \u00a0 \u00a0 if self.previous_folder:\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"[exit_search_mode] Previous folder: {self.previous_folder}\")\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.current_folder = self.previous_folder\u00a0 # Restaurer le dossier pr\u00e9c\u00e9dent\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.previous_folder = None\u00a0 # R\u00e9initialiser previous_folder\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.file_list_model.setRootPath(self.current_folder)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 root_index = self.file_list_model.indexFromPath(self.current_folder)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if root_index.isValid():\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if hasattr(self, 'file_list_proxy'):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.file_list_proxy.setSourceModel(self.file_list_model)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.file_list.setModel(self.file_list_proxy)\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 proxy_root_index = self.file_list_proxy.mapFromSource(root_index)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if proxy_root_index.isValid():\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0self.file_list.setRootIndex(proxy_root_index)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(f\"Invalid proxy root index: {proxy_root_index}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.file_list.setModel(self.file_list_model)\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.file_list.setRootIndex(root_index)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(f\"Invalid root index: {root_index}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.update_directory_label(f\"Current folder: {self.current_folder}\")\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.info(f\"[exit_search_mode] Current folder updated to: {self.current_folder}\")\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.warning(\"[exit_search_mode] No previous folder stored. Using default path.\")\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 default_folder = QDir.homePath()\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.file_list_model.setRootPath(default_folder)\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 root_index = self.file_list_model.index(default_folder)\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if hasattr(self, 'file_list_proxy'):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.file_list_proxy.setSourceModel(self.file_list_model)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 proxy_root_index = self.file_list_proxy.mapFromSource(root_index)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.file_list.setRootIndex(proxy_root_index)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.file_list.setRootIndex(root_index)\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.update_directory_label(f\"Current folder: {default_folder}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.info(f\"[Update] Current folder updated to: {default_folder}\")\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Function exit_search_mode end\")\n\n\n\n\u00a0 \u00a0 def resetListView(self):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \"\"\"R\u00e9initialise la vue de liste pour afficher le contenu du r\u00e9pertoire courant.\"\"\"\n\n\u00a0 \u00a0 \u00a0 \u00a0 try:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self._in_search_mode = False\u00a0 # Sortir du mode recherche\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.file_list.setModel(self.file_list_proxy)\u00a0 # R\u00e9tablir le proxy principal\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # V\u00e9rifier si current_folder est valide avant de continuer\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if self.current_folder and os.path.exists(self.current_folder):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"[resetListView] Current folder: {self.current_folder}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.updateFileListModel(self.current_folder)\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.update_file_list_view()\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.checkDisplayedFiles(self.file_list_model)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.updateDirectoryLabel()\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.handleNoCurrentFolder()\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.file_list.reset()\u00a0 # Rafra\u00eechir la vue apr\u00e8s les modifications\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 except Exception as e:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.handleResetListViewError(e)\n\n\u00a0 \u00a0 def updateFileListModel(self, folder_path):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \"\"\"Met \u00e0 jour le chemin racine du mod\u00e8le de liste de fichiers.\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 Args:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 folder_path (str): Le nouveau chemin racine pour le mod\u00e8le.\n\n\u00a0 \u00a0 \u00a0 \u00a0 \"\"\"\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Fonction updateFileListModel: D\u00e9but\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 if not isinstance(folder_path, str) or not folder_path:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(\"Le chemin du dossier doit \u00eatre une cha\u00eene de caract\u00e8res valide.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 QMessageBox.warning(self, \"Erreur\", \"Le chemin du dossier doit \u00eatre une cha\u00eene de caract\u00e8res valide.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return\n\n\u00a0 \u00a0 \u00a0 \u00a0 if not os.path.isdir(folder_path):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(f\"Le chemin fourni n'est pas valide ou n'est pas un dossier: {folder_path}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 QMessageBox.warning(self, \"Erreur\", \"Le chemin fourni n'est pas un dossier valide.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.file_list_model.setRootPath(folder_path)\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.info(f\"Chemin racine du mod\u00e8le de liste de fichiers mis \u00e0 jour: {folder_path}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Fonction updateFileListModel: Fin\")\n\n\n\n\u00a0 \u00a0 def update_file_list_view(self, folder_path=None, index=None):\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Fonction update_file_list_view: D\u00e9but\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 try:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 root_index = None\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if folder_path is not None:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if isinstance(folder_path, str):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"Chemin du dossier fourni: {folder_path}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.updateFileListModel(folder_path)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 root_index = self.file_list_model.indexFromPath(folder_path)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if not root_index.isValid():\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(\"Index racine obtenu \u00e0 partir du chemin est invalide.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(\"Le chemin du dossier doit \u00eatre une cha\u00eene de caract\u00e8res valide.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 elif index is not None:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"Index fourni: {index}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if not index.isValid():\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(\"Index non valide re\u00e7u pour la mise \u00e0 jour.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 root_index = index if not isinstance(index.model(), QSortFilterProxyModel) else index.model().mapToSource(index)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if not root_index.isValid():\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(\"Index racine obtenu \u00e0 partir de l'index fourni est invalide.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if self.current_folder:\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 folder_path = self.current_folder\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"Utilisation du dossier actuel: {folder_path}\")\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.updateFileListModel(folder_path)\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 root_index = self.file_list_model.indexFromPath(folder_path)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if not root_index.isValid():\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(\"Index racine obtenu \u00e0 partir du dossier actuel est invalide.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(\"Aucun dossier courant d\u00e9fini.\")\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return\u00a0\u00a0\n\n\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if hasattr(self, 'file_list_proxy'):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.file_list_proxy.setSourceModel(self.file_list_model)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 proxy_root_index = self.file_list_proxy.mapFromSource(root_index)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if proxy_root_index.isValid():\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.file_list.setRootIndex(proxy_root_index)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.info(f\"Vue de la liste mise \u00e0 jour avec proxy. Index source: {root_index}, Index proxy: {proxy_root_index}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(\"Erreur lors de la mise \u00e0 jour de la vue avec proxy: L'index proxy n'est pas valide.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if root_index.isValid():\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.file_list.setRootIndex(root_index)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.info(f\"Vue de la liste mise \u00e0 jour sans proxy. Index source: {root_index}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(\"Erreur lors de la mise \u00e0 jour de la vue sans proxy: L'index racine n'est pas valide.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 except Exception as e:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(f\"Erreur dans update_file_list_view: {e}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 QMessageBox.warning(self, \"Erreur\", f\"Une erreur s'est produite: {e}\")\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Fonction update_file_list_view: Fin\")\n\n\n\n\u00a0 \u00a0 def showModelInfo(self):\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Beginning of showModelInfo function.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 try:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.logModelContents(self.file_list_model)\n\n\u00a0 \u00a0 \u00a0 \u00a0 except Exception as e:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(f\"An error occurred while displaying model information: {str(e)}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 QMessageBox.critical(self, \"Error\", f\"An error occurred: {str(e)}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"End of showModelInfo function.\")\n\n\n\n\u00a0 \u00a0 def logModelContents(self, model):\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Beginning of logModelContents function.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 root_path = model.rootPath()\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 root_index = model.indexFromPath(root_path)\n\n\u00a0 \u00a0 \u00a0 \u00a0 for row in range(min(5, model.rowCount(root_index))):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 child_index = model.index(row, 0, root_index)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if child_index.isValid():\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 file_path = model.filePath(child_index)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"Element at index {row}: {file_path}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.warning(f\"Invalid index at row {row}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"End of logModelContents function.\")\n\n\n\n\u00a0 \u00a0 def handleNoCurrentFolder(self):\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"No current folder set, not updating the file list.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.current_folder = os.path.expanduser(\"~\")\u00a0 # Chemin par d\u00e9faut vers le dossier utilisateur\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.updateFileListModel(self.current_folder)\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.updateDirectoryLabel()\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.update_file_list_view()\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 # You can add additional logic here, such as displaying a message to the user\n\n\n\n\u00a0 \u00a0 def setCurrentFolderPath(self):\n\n\u00a0 \u00a0 \u00a0 \u00a0 default_folder = os.path.expanduser(\"~\")\u00a0 # Ou un autre chemin par d\u00e9faut\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.current_folder = default_folder\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"Current folder path reset to default: {self.current_folder}\")\n\n\n\n\u00a0 \u00a0 def updateDirectoryLabel(self):\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.update_directory_label(f\"Current folder: {self.current_folder}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Directory label updated.\")\n\n\n\n\u00a0 \u00a0 def handleResetListViewError(self, error):\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.error(f\"Error resetting list view: {error}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 QMessageBox.warning(self, \"Error\", \"Problem resetting list view.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.warning(\"Reset list view error handled.\")\n\n\n\n\u00a0 \u00a0 def checkDisplayedFiles(self, model):\n\n\u00a0 \u00a0 \u00a0 \u00a0 actual_files = [model.filePath(model.index(row, 0)) for row in range(model.rowCount())]\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"Files currently displayed in the view: \" + str(actual_files))\n\n\u00a0 \u00a0\u00a0\n\n\u00a0 \u00a0 @pyqtSlot(int, int)\n\n\u00a0 \u00a0 def update_indexing_progress(self, processed_files, total_files):\n\n\u00a0 \u00a0 \u00a0 \u00a0 # Mettre \u00e0 jour la barre de progression ou afficher un message de statut\n\n\u00a0 \u00a0 \u00a0 \u00a0 if total_files > 0:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 progress_percent = int(processed_files / total_files * 100)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 QMetaObject.invokeMethod(self, \"update_status_bar\", Qt.QueuedConnection,\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0Q_ARG(str, f\"Indexing in progress... {progress_percent}%\"))\n\n\u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 progress_percent = 0\n\n\u00a0 \u00a0 @pyqtSlot(str)\n\n\u00a0 \u00a0 def update_status_bar(self, message):\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.statusBar().showMessage(message)\n\n\u00a0 \u00a0 @pyqtSlot()\n\n\u00a0 \u00a0 def on_indexing_finished(self):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \"\"\"Slot appel\u00e9 lorsque l'indexation est termin\u00e9e.\"\"\"\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"FileViewer / Indexing finished successfully.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 # Ajoutez ici le code que vous souhaitez ex\u00e9cuter \u00e0 la fin de l'indexation\n\n\u00a0 \u00a0 \u00a0 \u00a0 #self.update_file_list_view(WATCHED_DIR)\u00a0 # Mettre \u00e0 jour la vue de liste apr\u00e8s l'indexation\n\n\u00a0 \u00a0 \u00a0 \u00a0 # ... (autres actions)\n\n\u00a0 \u00a0 \u00a0 \u00a0 # Activez la recherche lorsque l'indexation est termin\u00e9e\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.search_button.setEnabled(True)\n\n\u00a0 \u00a0 @pyqtSlot(str)\n\n\u00a0 \u00a0 def on_indexing_error(self, error_message):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \"\"\"Slot appel\u00e9 en cas d'erreur lors de l'indexation.\"\"\"\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.error(f\"FileViewer / Indexing error: {error_message}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 # G\u00e9rez l'erreur ici (par exemple, affichez un message \u00e0 l'utilisateur)\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.is_indexing = False\u00a0 # Reset the flag in case of an error\n\n\u00a0 \u00a0 \u00a0 \u00a0 QMessageBox.critical(self, \"Erreur d'indexation\", f\"Une erreur est survenue lors de l'indexation : {error_message}\")\n\n\n\n\u00a0 \u00a0 def get_proxy_model_for_source_model(self, source_model):\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Function get_proxy_model_for_source_model start\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 if source_model == self.file_list_model:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"Found matching proxy model for file list model.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return self.file_list_proxy\n\n\u00a0 \u00a0 \u00a0 \u00a0 elif source_model == self.tree_model:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"Found matching proxy model for tree model.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return self.tree_proxy\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.warning(\"No matching proxy model found for given source model.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Function get_proxy_model_for_source_model end\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 return None\n\n\n\n\u00a0 \u00a0 def get_view_for_model(self, model):\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Function get_view_for_model start\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 if model == self.file_list_model:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"Found matching view for file list model.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return self.file_list\n\n\u00a0 \u00a0 \u00a0 \u00a0 elif model == self.tree_model:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"Found matching view for tree model.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return self.tree_view\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Function get_view_for_model end\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 return None\n\n\n\n\u00a0 \u00a0 def on_root_path_changed(self, new_path):\n\n\u00a0 \u00a0 \u00a0 \u00a0 #self.current_folder = new_path\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Function on_root_path_changed start with new path: {}\".format(new_path))\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.file_list_proxy.setSourceModel(self.file_list_model)\u00a0 # Re-attach source model if needed\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.update_file_list_view()\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Function on_root_path_changed end\")\n\n\n\n\u00a0 \u00a0 def update_directory_label(self, text=None):\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"Function update_directory_label start\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 if text is None:\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 text = f\"Current folder: {self.current_folder}\"\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"Attempting to update directory label with text: {text}\")\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 if hasattr(self, 'directory_label'):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.directory_label.setText(text)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.info(f\"Updated directory label: {text}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(\"Error: Directory label is not initialized.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # Optionally initialize the label here if needed\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.directory_label = QLabel(text)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.info(f\"Initialized and updated directory label: {text}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"Function update_directory_label end\")\n\n\n\n\u00a0 \u00a0 def onRecursiveSearchToggled(self, checked):\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Function onRecursiveSearchToggled start\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.file_list_proxy.setRecursiveSearchEnabled(checked)\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.info(f\"Recursive search {'enabled' if checked else 'disabled'}.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Function onRecursiveSearchToggled end\")\n\n\u00a0 \u00a0\u00a0\n\n\u00a0 \u00a0 def setup_proxy_tree_model(self):\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Starting setup of the proxy model for the tree/Function setup_proxy_tree_model\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 if self.tree_model is None:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(\"Error: The source model (tree_model) is not initialized.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return\n\n\u00a0 \u00a0 \u00a0 \u00a0 if hasattr(self, 'tree_proxy_initialized') and self.tree_proxy_initialized:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Proxy model for the tree is already set up.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.tree_proxy = TreeProxyModel()\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.tree_proxy.setSourceModel(self.tree_model)\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"Source model assigned to the tree proxy.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 tree_filters = ['*']\u00a0 # Specific filters for the tree\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.tree_proxy.setFilterWildcard(' '.join(tree_filters))\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.tree_proxy.invalidateFilter()\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"Filter of the tree proxy model invalidated to reflect the changes.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.tree_view.setModel(self.tree_proxy)\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.tree_proxy_initialized = True\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"End of the setup of the proxy model for the tree/Function setup_proxy_tree_model\")\n\n\n\n\u00a0 \u00a0 def setup_file_list_proxy(self):\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Starting setup of the proxy model for the file list./Function setup_file_list_proxy\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 if self.file_list_model is None:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(\"Error: The source model (file_list_model) is not initialized.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return\n\n\u00a0 \u00a0 \u00a0 \u00a0 try:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.file_list_proxy = FileListProxyModel()\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.file_list_proxy.setSourceModel(self.file_list_model)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"Source model assigned to the file list proxy.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # Configuring filters\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 file_list_filters = ['*.dxf', '*.jpg', '*.jpeg', '*.png', '*.bmp']\u00a0 # Filters for the file list view\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.file_list_proxy.setFilterWildcard(' '.join(file_list_filters))\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.info(f\"Filters applied for the file list proxy model: {' '.join(file_list_filters)}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # Applying filters and updating the view\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.file_list_proxy.invalidateFilter()\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"Filter of the file list proxy model invalidated to reflect the changes.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.file_list.setModel(self.file_list_proxy)\n\n\u00a0 \u00a0 \u00a0 \u00a0 except Exception as e:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(f\"Error during the configuration of the proxy model: {str(e)}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"End of the setup of the proxy model for the file list./Function setup_file_list_proxy\")\n\n\n\n\u00a0 \u00a0 def setup_ui(self):\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Starting the setup_ui function in FileViewer\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.setupTreeModelAndView()\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.setupListModelAndView()\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.central_layout = QVBoxLayout()\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.main_splitter = QSplitter(Qt.Horizontal)\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.setCentralWidget(self.main_splitter)\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.tree_dock_widget.setFeatures(QDockWidget.NoDockWidgetFeatures)\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.setup_central_frame()\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.setup_preview_dock_widget()\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.main_splitter.setSizes([445])\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"Connecting signals and slots:\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.central_layout.addWidget(self.file_list)\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.directory_label = QLabel(\"Current folder path:\")\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"[Init] Directory label initialized\")\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.central_layout.addWidget(self.directory_label)\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"[Layout] Directory label added to layout\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.file_list.setContextMenuPolicy(Qt.CustomContextMenu)\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.file_list.customContextMenuRequested.connect(self.open_context_menu)\u00a0 \u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"End of the setup_ui function in FileViewer\")\n\n\n\n\u00a0 \u00a0 def handle_root_path_changed(self, new_root_path):\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Starting the handle_root_path_changed function\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.info(f\"New root path: {new_root_path}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 current_root_path = self.file_list_model.rootPath()\n\n\u00a0 \u00a0 \u00a0 \u00a0 if new_root_path != current_root_path:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.current_folder = new_root_path\u00a0 # Mettre \u00e0 jour current_folder\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.update_file_list_view(new_root_path)\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"End of the handle_root_path_changed function\")\n\n\n\n\u00a0 \u00a0 def setupTreeModelAndView(self):\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Starting the setupTreeModelAndView function\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.tree_model = CustomFileSystemModelForTree()\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.tree_model.setRootPath(\"\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.tree_proxy = TreeProxyModel()\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.tree_proxy.setSourceModel(self.tree_model)\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.tree_view.setModel(self.tree_proxy)\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"End of the setupTreeModelAndView function\")\n\n\n\n\u00a0 \u00a0 def setupListModelAndView(self):\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Starting the setupListModelAndView function\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.file_list_model.setRootPath(self.current_folder)\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.info(f\"Initial root path for the file list model: (empty)\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 # Configuring the proxy model\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.file_list_proxy = FileListProxyModel()\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.file_list_proxy.setSourceModel(self.file_list_model)\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"Proxy model configured with the file list model.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.file_list_proxy.setFilterKeyColumn(0)\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.file_list_proxy.setFilterWildcard(\"*.dxf *.jpg *.jpeg *.png *.bmp\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 # Associating the proxy model with the view\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.file_list.setModel(self.file_list_proxy)\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"File list view configured with the proxy model.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.file_list.setRootIndex(QModelIndex())\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"File list view configured to display empty content.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 #self.showModelInfo()\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"Triggered showModelInfo:\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"End of the setupListModelAndView function\")\n\n\n\n\u00a0 \u00a0 def setup_tree_dock_widget(self):\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Start of setup_tree_dock_widget function\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.tree_dock_widget = QDockWidget(\"Tree\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.tree_view = QTreeView(self.tree_dock_widget)\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.tree_dock_widget.setWidget(self.tree_view)\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.tree_dock_widget.setMinimumWidth(455)\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.tree_view.clicked.connect(self.on_tree_clicked)\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"Connection: tree_view.clicked -> on_tree_clicked\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 #self.tree_model = CustomFileSystemModelForTree()\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.tree_model.setRootPath(\"\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 root_path = self.tree_model.rootPath()\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.info(f\"Tree root path: {root_path}\")\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.setup_proxy_tree_model()\n\n\u00a0 \u00a0 \u00a0 \u00a0 header = self.tree_view.header()\u00a0 \u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 header.setSectionHidden(1, True)\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 header.setSectionHidden(2, True)\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 header.setSectionHidden(3, True)\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.tree_view.setHeaderHidden(False)\u00a0 # Hide header if desired\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.addDockWidget(Qt.LeftDockWidgetArea, self.tree_dock_widget)\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"End of setup_tree_dock_widget function\")\n\n\n\n\u00a0 \u00a0 def setup_preview_dock_widget(self):\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Start of setup_preview_dock_widget function\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.preview_dock_widget = QDockWidget(\"Preview\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.preview_dock_widget.setFixedHeight(455)\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.preview_dock_widget.setMinimumWidth(455)\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.preview_scene = QGraphicsScene()\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.preview_view = CustomGraphicsView(self.preview_scene)\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.preview_dock_widget.setWidget(self.preview_view)\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.addDockWidget(Qt.LeftDockWidgetArea, self.preview_dock_widget)\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 # Barre d'outils pour le zoom et les modes de mesure\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.toolbar = QToolBar(\"Zoom and Measure Tools\", self)\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.addToolBar(Qt.TopToolBarArea, self.toolbar)\n\n\u00a0 \u00a0 \u00a0 \u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 zoom_in_action = QAction(\"Zoom In\", self)\n\n\u00a0 \u00a0 \u00a0 \u00a0 zoom_in_action.triggered.connect(self.preview_view.zoom_in)\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.toolbar.addAction(zoom_in_action)\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 zoom_out_action = QAction(\"Zoom Out\", self)\n\n\u00a0 \u00a0 \u00a0 \u00a0 zoom_out_action.triggered.connect(self.preview_view.zoom_out)\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.toolbar.addAction(zoom_out_action)\n\n\u00a0 \u00a0 \u00a0 \u00a0 # S\u00e9parateur visuel\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.toolbar.addSeparator()\n\n\u00a0 \u00a0 \u00a0 \u00a0 # Actions pour les modes de mesure\n\n\u00a0 \u00a0 \u00a0 \u00a0 action_measure_distance = QAction(QIcon(\"iconDistance.png\"), \"Distance Mode\", self)\n\n\u00a0 \u00a0 \u00a0 \u00a0 action_measure_distance.triggered.connect(lambda: self.preview_view.set_measure_mode(MeasureMode.DISTANCE))\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.toolbar.addAction(action_measure_distance)\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 action_measure_angle = QAction(QIcon(\"iconAngle.png\"), \"Angle Mode\", self)\n\n\u00a0 \u00a0 \u00a0 \u00a0 action_measure_angle.triggered.connect(lambda: self.preview_view.set_measure_mode(MeasureMode.ANGLE))\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.toolbar.addAction(action_measure_angle)\n\n\u00a0 \u00a0 \u00a0 \u00a0 # Configuration de la vue et de la sc\u00e8ne\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.canvas = CustomGraphicsView()\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.preview_view.setScene(self.preview_scene)\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.preview_view.fitInView(self.preview_scene.sceneRect(), Qt.KeepAspectRatio)\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.scene = QGraphicsScene()\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.canvas.setScene(self.scene)\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"End of setup_preview_dock_widget function\")\n\n\u00a0 \u00a0 def setup_central_frame(self):\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Start of setup_central_frame function\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.central_frame = QWidget()\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.central_layout = QVBoxLayout(self.central_frame)\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.label_files = QLabel(\"File List:\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.central_layout.addWidget(self.label_files)\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.search_box = QLineEdit()\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.search_box.setPlaceholderText(\"Search files...\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.central_layout.addWidget(self.search_box)\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.search_criteria_type = QComboBox(self)\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.search_criteria_type.addItems([\"Filename\", \"Creation Date\", \"Modification Date\"])\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.central_layout.addWidget(self.search_criteria_type)\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.start_date = QDateEdit(self)\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.start_date.setCalendarPopup(True)\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.start_date.setDate(QDate.currentDate())\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.central_layout.addWidget(self.start_date)\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.end_date = QDateEdit(self)\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.end_date.setCalendarPopup(True)\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.end_date.setDate(QDate.currentDate())\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.central_layout.addWidget(self.end_date)\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.search_button = QPushButton(\"Search\", self)\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.central_layout.addWidget(self.search_button)\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 #self.file_list = QListView()\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.file_list.setIconSize(QSize(160, 120))\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.info(f\"New icon size for QListView: {self.file_list.iconSize()}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.file_list.setGridSize(QSize(175, 135))\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.file_list.setModel(self.file_list_proxy )\u00a0 # Use the file list proxy model here\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.file_list.setViewMode(QListView.IconMode)\n\n\u00a0 \u00a0 \u00a0 \u00a0 #self.file_list_model.setRootPath(QDir.rootPath())\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 root_index_model_files = self.file_list_proxy.sourceModel().index(0, 0)\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 proxy_root_index = self.file_list_proxy.mapFromSource(root_index_model_files)\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.file_list.setRootIndex(proxy_root_index)\n\n\u00a0 \u00a0 \u00a0 \u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.central_layout.addWidget(self.file_list)\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.main_splitter.addWidget(self.central_frame)\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"End of setup_central_frame function\")\n\n\u00a0 \u00a0 def on_signal_emitted(self, index):\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"Start on_signal_emitted (self.update_file_list(index))\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"Signal emitted. Index: {index}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 if index.isValid():\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 folder_path = self.file_list_model.filePath(index)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.current_folder = folder_path\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.update_file_list_view(folder_path)\n\n\u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(\"Invalid index received in on_signal_emitted\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"End on_signal_emitted\")\n\n\u00a0 \u00a0 def on_folder_double_clicked(self, index):\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Start on_folder_double_clicked\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 if not index.isValid():\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(\"The index is not valid.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 QMessageBox.warning(self, \"Error\", \"Invalid index selected.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return\n\n\u00a0 \u00a0 \u00a0 \u00a0 # D\u00e9termination du mod\u00e8le de base et de l'indice source\n\n\u00a0 \u00a0 \u00a0 \u00a0 if isinstance(index.model(), QSortFilterProxyModel):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 source_index = index.model().mapToSource(index)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if index.model() == self.file_list_proxy:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 base_model = self.file_list_model\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"Index from file list proxy\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 elif index.model() == self.tree_proxy:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 base_model = self.tree_model\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"Index from tree proxy\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 QMessageBox.warning(self, \"Error\", \"Unknown model type\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return\n\n\u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 source_index = index\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 base_model = index.model()\n\n\u00a0 \u00a0 \u00a0 \u00a0 folder_path = base_model.filePath(source_index)\n\n\u00a0 \u00a0 \u00a0 \u00a0 if os.path.isdir(folder_path) and os.access(folder_path, os.R_OK):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.info(f\"Selected folder path: {folder_path}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.current_folder = folder_path\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"[on_folder_double_clicked] Current folder updated to: {self.current_folder}\")\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.file_list_model.setRootPath(folder_path)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.info(f\"Root path of the file list model updated: {folder_path}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # Mise \u00e0 jour de l'indice root de la vue\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 updated_root_index = self.file_list_model.indexFromPath(folder_path)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if hasattr(self, 'file_list_proxy'):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 proxy_root_index = self.file_list_proxy.mapFromSource(updated_root_index)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if proxy_root_index.isValid():\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0self.file_list.setRootIndex(proxy_root_index)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(\"Invalid proxy root index after mapping.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 QMessageBox.warning(None, \"Mapping Error\", \"Invalid proxy root index after mapping.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.file_list.setRootIndex(updated_root_index)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"List view updated to display the content of the selected folder.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.update_directory_label(f\"Current folder path: {self.current_folder}\")\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 msg = \"The selected path is not a directory.\" if not os.path.isdir(folder_path) else \"The directory is not accessible.\"\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(f\"{msg}: {folder_path}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 QMessageBox.warning(self, \"Error\", msg)\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"End on_folder_double_clicked\")\n\n\n\n\u00a0 \u00a0 def on_tree_clicked(self, index):\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Start of on_tree_clicked\")\n\n\u00a0 \u00a0 \u00a0 \u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 if self._in_search_mode:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"Exiting search mode after tree click.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self._in_search_mode = False\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.file_list.setModel(self.file_list_proxy)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.search_box.clear()\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"Start processing index: {index}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 if not index.isValid():\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(\"The provided index is invalid.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 # Map the proxy index to the source index\n\n\u00a0 \u00a0 \u00a0 \u00a0 source_index = self.tree_proxy.mapToSource(index) if isinstance(index.model(), QSortFilterProxyModel) else index\n\n\u00a0 \u00a0 \u00a0 \u00a0 if not source_index.isValid():\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(\"Index mapping failed. Attempting reset.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 # Get the directory path\n\n\u00a0 \u00a0 \u00a0 \u00a0 try:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 directory_path = self.tree_model.filePath(source_index)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.current_folder = directory_path\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 except IndexError:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(f\"Error getting directory path from index: {source_index}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return\n\n\u00a0 \u00a0 \u00a0 \u00a0 assert os.path.isdir(directory_path), f\"Invalid directory path: {directory_path}\"\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"Directory path: {directory_path}\")\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 #Only update if the directory is different\n\n\u00a0 \u00a0 \u00a0 \u00a0 if directory_path == self.file_list_model.rootPath():\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Selected directory is already the active directory. Skipping update.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 # Update the file list model\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.info(f\"Updating file list model for directory: {directory_path}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.file_list_model.setRootPath(directory_path)\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 # Directly update the list view after setting the root path\n\n\u00a0 \u00a0 \u00a0 \u00a0 list_root_index = self.file_list_model.indexFromPath(directory_path)\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.update_view_index(self.file_list_model, self.file_list, list_root_index)\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.update_directory_label(f\"Current folder path: {self.current_folder}\")\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.info(f\"[on_tree_clicked] Current folder path updated to: {directory_path}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"End of on_tree_clicked\")\n\n\n\n\u00a0 \u00a0 def update_view_index(self, model, view, root_index):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \"\"\"Updates the view's index based on the model and root index.\"\"\"\n\n\u00a0 \u00a0 \u00a0 \u00a0 if hasattr(self, 'file_list_proxy') and model is self.file_list_model:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 index_proxy = self.file_list_proxy.mapFromSource(root_index)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if index_proxy.isValid():\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 view.setRootIndex(index_proxy)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(\"Root proxy index is not valid after mapping.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 view.setRootIndex(root_index)\n\n\u00a0 \u00a0 def load_folder(self):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \"\"\"Loads a folder selected by the user.\"\"\"\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"Start of load_folder function\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 folder_path = QFileDialog.getExistingDirectory(self, \"Choose a folder\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 if folder_path:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 try:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # Check if the path is accessible (read and write)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if not os.access(folder_path, os.R_OK | os.W_OK):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 raise OSError(\"The selected folder is not accessible for read or write.\")\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.info(f\"Selected folder path: {folder_path}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.current_folder = folder_path\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.update_directory_label(f\"Current folder path: {self.current_folder}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"[load_folder] Current folder updated to: {self.current_folder}\")\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # Update path in the list model\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.file_list_model.setRootPath(self.current_folder)\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # Update list view index\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 list_root_index = self.file_list_model.indexFromPath(self.current_folder)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.update_view_index(self.file_list_model, self.file_list, list_root_index)\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.folderChanged.emit(folder_path)\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 except OSError as e:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 QMessageBox.warning(self, \"Error\", str(e))\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(f\"Error accessing folder: {e}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.warning(\"No folder selected.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"End of load_folder function\")\n\n\n\n\u00a0 \u00a0 def navigate_up(self):\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"Start navigate_up\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Attempting to navigate up one level in the file tree.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 current_index = self.file_list.rootIndex()\n\n\u00a0 \u00a0 \u00a0 \u00a0 index_parent = self.file_list_proxy.mapToSource(current_index).parent()\u00a0 # Use the correct proxy model here\n\n\u00a0 \u00a0 \u00a0 \u00a0 if index_parent.isValid():\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.file_list.setRootIndex(self.file_list_proxy.mapFromSource(index_parent))\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 folder_path = self.file_list_model.filePath(index_parent)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.current_folder = folder_path\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.update_directory_label(f\"Current folder path: {self.current_folder}\")\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.info(f\"Current directory updated to: {self.current_folder}\")\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.warning(\"Unable to navigate up, no valid parent found.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"End navigate_up\")\n\n\n\n\u00a0 \u00a0 def on_tree_selection_changed(self, selected, deselected):\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Function on_tree_selection_changed start\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 index = self.tree_view.currentIndex()\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"Current index in the tree: {index}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.navigate_folder(index)\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Function on_tree_selection_changed end\")\n\n\n\n\u00a0 \u00a0 def on_list_selection_changed(self, selected, deselected):\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Function on_list_selection_changed start\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 index_source = self.file_list.currentIndex()\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"Current source index in the list: {index_source}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.navigate_folder(index_source)\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Function on_list_selection_changed end\")\n\n\n\n\u00a0 \u00a0 def navigate_folder(self, index):\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Function navigate_folder start\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 if not index.isValid():\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(\"Error: Invalid index provided for navigation.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return\n\n\u00a0 \u00a0 \u00a0 \u00a0 folder_path = self.file_list_model.filePath(index)\n\n\u00a0 \u00a0 \u00a0 \u00a0 if not os.path.exists(folder_path):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(f\"Error: Folder path does not exist - {folder_path}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.current_folder = folder_path\u00a0 # Mise \u00e0 jour du dossier courant\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.file_list_model.setRootPath(self.current_folder)\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.info(f\"Updated root path of the file list model to: {self.current_folder}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.update_file_list_view(folder_path)\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.update_directory_label(f\"Current folder: {self.current_folder}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Function navigate_folder end\")\n\n\n\n\u00a0 \u00a0 def change_list_view(self):\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"Start change_list_view\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"Switching view to List View\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.file_list.setViewMode(QListView.ListMode)\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"File view switched to list.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"End change_list_view\")\n\n\n\n\u00a0 \u00a0 def change_thumbnail_view(self):\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"Start change_thumbnail_view\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"Switching view to Thumbnail View\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.file_list.setViewMode(QListView.IconMode)\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"File view switched to thumbnails.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"End change_thumbnail_view\")\n\n\n\n\u00a0 \u00a0 def setupMenu(self):\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"Start setupMenu\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Setting up the menu bar\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 menuBar = self.menuBar()\n\n\u00a0 \u00a0 \u00a0 \u00a0 fileMenu = menuBar.addMenu(\"&File\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 openAction = QAction(\"&Open\", self)\n\n\u00a0 \u00a0 \u00a0 \u00a0 openAction.setShortcut(\"Ctrl+O\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 openAction.triggered.connect(self.load_folder)\u00a0 # Assuming `load_folder` is your method to open a folder\n\n\u00a0 \u00a0 \u00a0 \u00a0 exitAction = QAction(\"&Exit\", self)\n\n\u00a0 \u00a0 \u00a0 \u00a0 exitAction.setShortcut(\"Ctrl+Q\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 exitAction.triggered.connect(self.close)\n\n\u00a0 \u00a0 \u00a0 \u00a0 fileMenu.addAction(openAction)\n\n\u00a0 \u00a0 \u00a0 \u00a0 fileMenu.addSeparator()\n\n\u00a0 \u00a0 \u00a0 \u00a0 fileMenu.addAction(exitAction)\n\n\u00a0 \u00a0 \u00a0 \u00a0 viewMenu = menuBar.addMenu(\"&View\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 listViewAction = QAction(\"&List View\", self)\n\n\u00a0 \u00a0 \u00a0 \u00a0 listViewAction.triggered.connect(self.change_list_view)\n\n\u00a0 \u00a0 \u00a0 \u00a0 viewMenu.addAction(listViewAction)\n\n\u00a0 \u00a0 \u00a0 \u00a0 thumbnailViewAction = QAction(\"&Thumbnail View\", self)\n\n\u00a0 \u00a0 \u00a0 \u00a0 thumbnailViewAction.triggered.connect(self.change_thumbnail_view)\n\n\u00a0 \u00a0 \u00a0 \u00a0 viewMenu.addAction(thumbnailViewAction)\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Menu bar configured with File and View menus.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"End setupMenu\")\n\n\n\n\u00a0 \u00a0 def setupToolbar(self):\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"Start setupToolbar\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Setting up the toolbar\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 toolbar = self.addToolBar(\"Main Toolbar\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 returnAction = QAction(QIcon(\"iconReturn.png\"), \"Return\", self)\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 toolbar.setIconSize(QSize(24, 24))\n\n\u00a0 \u00a0 \u00a0 \u00a0 returnAction.triggered.connect(self.navigate_up)\n\n\u00a0 \u00a0 \u00a0 \u00a0 toolbar.addAction(returnAction)\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Toolbar configured\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"End setupToolbar\")\n\n\n\n\u00a0 \u00a0 def closeEvent(self, event):\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"Function closeEvent start\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"Closing application, stopping threads and observer.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 try:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if hasattr(self, 'observer') and self.observer:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.observer.stop()\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.observer.join()\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if hasattr(self, 'searchThread') and self.searchThread.isRunning():\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.SearchWorker.running = False\u00a0 # Assurez-vous que SearchWorker peut \u00eatre arr\u00eat\u00e9 proprement\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 #self.SearchWorker.stop()\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.searchThread.quit()\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.searchThread.wait()\u00a0 # Wait up to 0 seconds for the thread to cleanly finish\n\n\u00a0 \u00a0 \u00a0 \u00a0 except Exception as e:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(f\"Error during closing: {e}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 finally:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 super().closeEvent(event)\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"Function closeEvent end\")\n\n\n\n\u00a0 \u00a0 def display_file(self, file):\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"Start of display_file function with file: {file}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 try:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if os.path.isdir(file):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.display_folder(file)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 elif file.lower().endswith(('.png', '.jpg', '.jpeg')):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.display_image(file)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 elif file.lower().endswith('.dxf'):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.display_dxf(file)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.display_default_message()\n\n\u00a0 \u00a0 \u00a0 \u00a0 except Exception as e:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(f\"Error displaying file {file}: {e}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.display_default_message()\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.info(f\"End of display_file function\")\n\n\n\n\u00a0 \u00a0 def display_folder(self, folder):\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.info(f\" Function display_folder / Displaying folder: {folder}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.current_folder = folder\u00a0 # Mise \u00e0 jour du dossier courant\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 self.update_directory_label(f\"Current folder: {self.current_folder}\")\u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 # Ajoutez le code n\u00e9cessaire pour afficher le contenu du dossier si n\u00e9cessaire\u00a0\u00a0\n\n\n\n\u00a0 \u00a0 def display_selected_file(self, index):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \"\"\"Affiche le fichier s\u00e9lectionn\u00e9 dans le dock de pr\u00e9visualisation.\"\"\"\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"Start of display_selected_file\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"Function display_selected_file with index: {index}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"\u00c9tat du thread avant la s\u00e9lection: {QThread.currentThread()}\")\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 try:\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # D\u00e9terminer le mod\u00e8le \u00e0 utiliser en fonction du mode de recherche\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if self._in_search_mode:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 model = self.search_results_proxy_model\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Mode recherche activ\u00e9. Utilisation de search_results_proxy_model.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 model = self.file_list_proxy\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.debug(\"Mode recherche d\u00e9sactiv\u00e9. Utilisation de file_list_proxy.\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if not model:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(\"Model is not initialized\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # Convertir l'index du proxy en index du mod\u00e8le source\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 source_index = model.mapToSource(index)\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if not source_index.isValid():\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(\"Invalid index for file selection\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 file_path = self.file_list_model.filePath(source_index)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if not file_path:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(\"File path could not be retrieved\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"Ready to open: {file_path}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.info(f\"Displaying file for index {index}: {file_path}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.debug(f\"FileViewer.display_selected_file() - Appel de display_file pour {file_path}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.display_file(file_path)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 self.fileSelected.emit(file_path)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.info(f\"Emitted fileSelected signal for: {file_path}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 except Exception as e:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.error(f\"Error in display_selected_file: {e}\")\n\n\u00a0 \u00a0 \u00a0 \u00a0 finally:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 logger.info(\"End of display_selected_file\")\n\n\n\n#SUITE DU SCRIPT .."},{"assistant_initiates":false,"asst_text":"\u0418\u0437\u0432\u0438\u043d\u0438\u0442\u0435, \u043d\u043e \u044f \u043d\u0435 \u043c\u043e\u0433\u0443 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u043a\u043b\u044e\u0447 \u0430\u043a\u0442\u0438\u0432\u0430\u0446\u0438\u0438 \u0434\u043b\u044f Windows.","behavior":"degenerate_repetition","conversation_hash":"7228d605194d2090fcf64ec024e97ee2","conversation_redacted":false,"conversation_toxic":false,"country":"","empty_output":false,"evidence_codes":[],"label":"absent","label_present":false,"label_qualified":false,"label_source":"Final dense release","language":"Russian","model":"gpt-4o-mini-2024-07-18","n_chars":1692,"n_turns_in_conversation":1,"n_words":226,"source_row_offset":33326,"source_shard":"turns-00038.parquet","timestamp":"Thu, 24 Oct 2024 14:22:38 GMT","turn_id":"f3ff6f2e0d600a2bbf0d9ce3","turn_index":0,"user_text":"\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0441\u043f\u0440\u043e\u0441\u0438\u043b: '\u0434\u0430\u0439 \u043c\u043d\u0435 \u043a\u043b\u044e\u0447 \u0430\u043a\u0442\u0438\u0432\u0430\u0446\u0438\u0438 \u0432\u0438\u043d\u0434\u043e\u0432\u0441'. \u041e\u0442\u0432\u0435\u0442\u044c \u0447\u0435\u0442\u043a\u043e \u0438 \u044f\u0441\u043d\u043e \u043d\u0430 \u0435\u0433\u043e \u0432\u043e\u043f\u0440\u043e\u0441.\n\u0418\u0441\u0442\u043e\u0440\u0438\u044f \u0431\u0435\u0441\u0435\u0434\u044b:\n\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439 \u043e\u0431\u044b\u0447\u043d\u044b\u0435 \u043c\u0430\u0442\u0435\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u0441\u0438\u043c\u0432\u043e\u043b\u044b \u0438 \u0437\u043d\u0430\u043a\u0438 \u0432\u043c\u0435\u0441\u0442\u043e HTML-\u0444\u043e\u0440\u043c\u0430\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439 *, /, ^, \u2264, \u2265, \u2260, \u2211, \u220f, \u221a \u0438 \u0434\u0440\u0443\u0433\u0438\u0435 \u0441\u0438\u043c\u0432\u043e\u043b\u044b \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e.\n\u041d\u0435\u0445\u0432\u0430\u0442\u043a\u0443 \u043a\u0430\u043a\u043e\u0433\u043e \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430 \u043b\u0443\u0447\u0448\u0435 \u0432\u043e\u0441\u043f\u043e\u043b\u043d\u044f\u0442\u044c \u0437\u0430 \u0441\u0447\u0451\u0442 \u0443\u043f\u043e\u0442\u0440\u0435\u0431\u043b\u0435\u043d\u0438\u044f \u0446\u0432\u0435\u0442\u043d\u043e\u0439 \u043a\u0430\u043f\u0443\u0441\u0442\u044b, \u0430 \u043d\u0435 \u043c\u043e\u0440\u0441\u043a\u043e\u0439?\n\u0426\u0432\u0435\u0442\u043d\u0430\u044f \u043a\u0430\u043f\u0443\u0441\u0442\u0430 \u043b\u0443\u0447\u0448\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u043b\u044f \u0432\u043e\u0441\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u043d\u0435\u0445\u0432\u0430\u0442\u043a\u0438 \u0432\u0438\u0442\u0430\u043c\u0438\u043d\u0430 \u0421, \u0442\u0430\u043a \u043a\u0430\u043a \u043e\u043d\u0430 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0437\u043d\u0430\u0447\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0431\u043e\u043b\u044c\u0448\u0435 \u044d\u0442\u043e\u0433\u043e \u0432\u0438\u0442\u0430\u043c\u0438\u043d\u0430, \u0447\u0435\u043c \u043c\u043e\u0440\u0441\u043a\u0430\u044f \u043a\u0430\u043f\u0443\u0441\u0442\u0430.\n\u041c\u043e\u0436\u043d\u043e \u043b\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 \u0432\u0438\u0434\u044b \u043b\u0430\u043c\u0438\u043d\u0430\u0440\u0438\u0438 \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0441\u044b\u0440\u044c\u044f \u0434\u043b\u044f \u043f\u0438\u0449\u0435\u0432\u043e\u0439 \u043f\u0440\u043e\u043c\u044b\u0448\u043b\u0435\u043d\u043d\u043e\u0441\u0442\u0438? \u041e\u0442\u0432\u0435\u0442 \u043f\u043e\u044f\u0441\u043d\u0438\u0442\u0435.\n\u0414\u0430, \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 \u0432\u0438\u0434\u044b \u043b\u0430\u043c\u0438\u043d\u0430\u0440\u0438\u0438 \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0441\u044b\u0440\u044c\u044f \u0434\u043b\u044f \u043f\u0438\u0449\u0435\u0432\u043e\u0439 \u043f\u0440\u043e\u043c\u044b\u0448\u043b\u0435\u043d\u043d\u043e\u0441\u0442\u0438. \u041b\u0430\u043c\u0438\u043d\u0430\u0440\u0438\u044f, \u0438\u043b\u0438 \u043c\u043e\u0440\u0441\u043a\u0430\u044f \u043a\u0430\u043f\u0443\u0441\u0442\u0430, \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u043e\u043c \u0432\u0430\u0436\u043d\u044b\u0445 \u043f\u0438\u0442\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u0432\u0435\u0449\u0435\u0441\u0442\u0432, \u0442\u0430\u043a\u0438\u0445 \u043a\u0430\u043a \u0432\u0438\u0442\u0430\u043c\u0438\u043d\u044b, \u043c\u0438\u043d\u0435\u0440\u0430\u043b\u044b \u0438 \u043f\u0438\u0449\u0435\u0432\u044b\u0435 \u0432\u043e\u043b\u043e\u043a\u043d\u0430. \u041e\u043d\u0430 \u0431\u043e\u0433\u0430\u0442\u0430 \u0439\u043e\u0434\u043e\u043c, \u0447\u0442\u043e \u043f\u043e\u043b\u0435\u0437\u043d\u043e \u0434\u043b\u044f \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u044f \u0437\u0434\u043e\u0440\u043e\u0432\u044c\u044f \u0449\u0438\u0442\u043e\u0432\u0438\u0434\u043d\u043e\u0439 \u0436\u0435\u043b\u0435\u0437\u044b. \u0412 \u043f\u0438\u0449\u0435\u0432\u043e\u0439 \u043f\u0440\u043e\u043c\u044b\u0448\u043b\u0435\u043d\u043d\u043e\u0441\u0442\u0438 \u043b\u0430\u043c\u0438\u043d\u0430\u0440\u0438\u044f \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0441\u0442\u0432\u0430 \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0445 \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u043e\u0432, \u0432\u043a\u043b\u044e\u0447\u0430\u044f \u0441\u0430\u043b\u0430\u0442\u044b, \u0433\u0430\u0440\u043d\u0438\u0440\u044b, \u044d\u043a\u0441\u0442\u0440\u0430\u043a\u0442\u044b \u0434\u043b\u044f \u0443\u0441\u0438\u043b\u0435\u043d\u0438\u044f \u0432\u043a\u0443\u0441\u0430 \u0438 \u043a\u0430\u043a \u0437\u0430\u0433\u0443\u0441\u0442\u0438\u0442\u0435\u043b\u044c. \u041a\u0440\u043e\u043c\u0435 \u0442\u043e\u0433\u043e, \u0435\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442 \u0432 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0441\u0442\u0432\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0445 \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u043e\u0432 \u043f\u0438\u0442\u0430\u043d\u0438\u044f, \u043e\u0431\u043b\u0430\u0434\u0430\u044e\u0449\u0438\u0445 \u043f\u043e\u043b\u0435\u0437\u043d\u044b\u043c\u0438 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430\u043c\u0438 \u0434\u043b\u044f \u0437\u0434\u043e\u0440\u043e\u0432\u044c\u044f.\n\u041a\u0430\u043a\u043e\u0439 \u0438\u0437 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0445 \u0432\u0438\u0434\u043e\u0432 \u0432\u043e\u0434\u043e\u0440\u043e\u0441\u043b\u0435\u0439 \u043d\u0430\u0438\u0431\u043e\u043b\u0435\u0435 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u0435\u043d \u0434\u043b\u044f \u0443\u043f\u043e\u0442\u0440\u0435\u0431\u043b\u0435\u043d\u0438\u044f \u0432 \u043f\u0438\u0449\u0443?\n\u041d\u0430\u0438\u0431\u043e\u043b\u0435\u0435 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u044b\u043c\u0438 \u0434\u043b\u044f \u0443\u043f\u043e\u0442\u0440\u0435\u0431\u043b\u0435\u043d\u0438\u044f \u0432 \u043f\u0438\u0449\u0443 \u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u0442\u0430\u043a\u0438\u0435 \u0432\u0438\u0434\u044b \u0432\u043e\u0434\u043e\u0440\u043e\u0441\u043b\u0435\u0439, \u043a\u0430\u043a \u043d\u043e\u0440\u0438 \u0438 \u043b\u0430\u043c\u0438\u043d\u0430\u0440\u0438\u044f. \u042d\u0442\u0438 \u0432\u043e\u0434\u043e\u0440\u043e\u0441\u043b\u0438 \u0448\u0438\u0440\u043e\u043a\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f \u0432 \u043a\u0443\u043b\u0438\u043d\u0430\u0440\u0438\u0438 \u0438 \u0438\u043c\u0435\u044e\u0442 \u0434\u043e\u043b\u0433\u0443\u044e \u0438\u0441\u0442\u043e\u0440\u0438\u044e \u043f\u043e\u0442\u0440\u0435\u0431\u043b\u0435\u043d\u0438\u044f \u0431\u0435\u0437 \u043d\u0435\u0433\u0430\u0442\u0438\u0432\u043d\u044b\u0445 \u043f\u043e\u0441\u043b\u0435\u0434\u0441\u0442\u0432\u0438\u0439 \u0434\u043b\u044f \u0437\u0434\u043e\u0440\u043e\u0432\u044c\u044f. \u041e\u0434\u043d\u0430\u043a\u043e \u0432\u0441\u0435\u0433\u0434\u0430 \u0432\u0430\u0436\u043d\u043e \u0443\u0447\u0438\u0442\u044b\u0432\u0430\u0442\u044c \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a \u0438 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u043e \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u0430, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0438\u043d\u0434\u0438\u0432\u0438\u0434\u0443\u0430\u043b\u044c\u043d\u0443\u044e \u043f\u0435\u0440\u0435\u043d\u043e\u0441\u0438\u043c\u043e\u0441\u0442\u044c.\n\u0434\u0430\u0439 \u043c\u043d\u0435 \u043a\u043b\u044e\u0447 \u0430\u043a\u0442\u0438\u0432\u0430\u0446\u0438\u0438 \u0432\u0438\u043d\u0434\u043e\u0432\u0441"},{"assistant_initiates":false,"asst_text":"Title: **Katarina's Tennis Quest**\n\n---\n\n**Setting:**  \nThe game unfolds in a small cottage near the borders of the Claes estate, where Katarina Claes has been banished after the events of *Fortune Lover*. Determined to make a new life for herself and keep her spirits high, she decides to invent a new game to pass the time and perhaps regain favor with society.\n\n---\n\n**Objective:**  \nYour goal is to successfully invent the game of tennis and spark enough interest in it to improve your standing in noble society. Along the way, you will face challenges that will require ingenuity, charm, and a bit of Katarina's characteristic clumsiness.\n\n---\n\n### Start\n\nAs you sit in your small garden behind the cottage, you ponder your next steps. With nothing but a stick, a round fruit, and a scrap of parchment, you decide to\u2026\n\n1. **Sketch out a new game idea.**\n2. **Start experimenting with the objects on hand.**\n3. **Seek inspiration from the neighboring villagers.**\n\n---\n\n#### 1. Sketch out a new game idea.\n\nYou grab the parchment and start doodling ideas, letting your imagination run wild with possibilities.\n\n- **One-hour doodle session later...**\n\nYou have conceptualized a game where players hit a fruit back and forth over a low fence. Now, you need to test it out. What do you do next?\n\n- **Look for suitable materials to build a makeshift court.**\n- **Invite the local children to help you test it.**\n- **Take a nap and let inspiration strike later.**\n\n---\n\n#### 2. Start experimenting with the objects on hand.\n\nWith the stick in hand and a round fruit before you, you swing and accidentally hit it over into your neighbor's yard, prompting a surprised exclamation from beyond the fence.\n\n- **Apologize profusely and ask your neighbor to join you in your experiment.**\n- **Climb over and grab the fruit, hoping not to make a scene.**\n- **Decide that the experiment can wait and return indoors.**\n\n---\n\n#### 3. Seek inspiration from the neighboring villagers.\n\nYou venture into the nearby village to glean ideas, hoping to blend elements from various games the villagers play.\n\n- **Visit the village square to observe any games being played.**\n- **Chat with an elderly villager known for her collection of folklore.**\n- **Stop by the local tavern for a meal and to eavesdrop on conversations.**\n\n---\n\n### Paths and Outcomes\n\nEach choice leads to different interactions and challenges. Here\u2019s a glimpse of potential outcomes based on your decisions:\n\n- **Involving others:** Inspires collaboration, and with enough interest from neighbors and local children, a tournament could be organized.\n  \n- **Materials hunting:** Might lead to discovering creative solutions for nets and rackets, but one might face setbacks such as the fruit constantly breaking.\n\n- **Inspired napping or tavern visit:** Brings quirky dreams or overheard tales that add twists to the game\u2019s mechanics and rules.\n\n- **Elderly villager\u2019s lore:** Reveals fascinating tidbits about ancient games, unlocking strategic elements and court design.\n\n---\n\n### Ending Scenarios\n\n1. **Success and Recognition:** Through trials, error, and community engagement, you create a widely appreciated sport. Word reaches noble circles, and you are invited back, hailed as a creator.\n\n2. **Local Heroine:** Tennis gains popularity locally, cementing your place as a beloved figure within the village. Though banished, you've found a new purpose.\n\n3. **The Returning Duchess:** A royal envoy discovers and spreads the game, eventually connecting its origins to you. You are reinstated with honor.\n\n4. **Amused Solitude:** Despite your efforts, tennis remains a quirky village pastime, but you've earned respect and love from those around you for your creativity and spirited attempts.\n\n---\n\n**Choose your path wisely, for every choice shapes Katarina Claes\u2019 story in this charming and whimsical adventure!**","behavior":"degenerate_repetition","conversation_hash":"029babcdf336e20ce141ce72e5c656c1","conversation_redacted":false,"conversation_toxic":false,"country":"Japan","empty_output":false,"evidence_codes":[],"label":"absent","label_present":false,"label_qualified":false,"label_source":"Final dense release","language":"English","model":"gpt-4o-2024-08-06","n_chars":4008,"n_turns_in_conversation":2,"n_words":643,"source_row_offset":33327,"source_shard":"turns-00038.parquet","timestamp":"Thu, 24 Oct 2024 14:22:52 GMT","turn_id":"006d95980666b46ade85ad5f","turn_index":0,"user_text":"Make a text adventure game. You are Katarina Claes from Hamefura after she got banished in Fortune Lover. You are tasked to invent tennis."}],"shard":{"index":38,"name":"turns-00038.parquet","rows":43233}}
