diff --git a/build.py b/build.py new file mode 100644 index 0000000..5a8c99e --- /dev/null +++ b/build.py @@ -0,0 +1,589 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Скрипт для сборки 32-битного приложения в один файл +""" + +import os +import sys +import shutil +import platform +import subprocess +import argparse +from pathlib import Path +import logging +from datetime import datetime + +class OneFileBuilder: + """Класс для сборки одного исполняемого файла""" + + def __init__(self, config_file='onefile_32bit.spec'): + self.config_file = config_file + self.project_root = Path.cwd() + self.dist_dir = self.project_root / 'dist' + self.build_dir = self.project_root / 'build' + + def print_banner(self): + """Вывод информационного баннера""" + print("=" * 60) + print(" СБОРКА 32-БИТНОГО ПРИЛОЖЕНИЯ В ОДИН ФАЙЛ") + print("=" * 60) + print(f"Проект: {self.project_root.name}") + print(f"Система: {platform.system()} {platform.architecture()[0]}") + print(f"Python: {platform.python_version()}") + print("=" * 60) + + def check_requirements(self): + """Проверка необходимых компонентов""" + print("\n🔍 Проверка требований...") + + # Проверка Python архитектуры + if platform.system() == 'Windows' and platform.architecture()[0] != '32bit': + print("⚠️ ВНИМАНИЕ: Используется 64-битный Python!") + print("Для сборки 32-битного EXE рекомендуется:") + print("1. Установить 32-битный Python с python.org") + print("2. Используйте эту версию Python для сборки") + response = input("Продолжить сборку? (y/n): ") + if response.lower() != 'y': + return False + + # Проверка PyInstaller + try: + import PyInstaller + print(f"✓ PyInstaller {PyInstaller.__version__}") + except ImportError: + print("✗ PyInstaller не установлен") + print("Установка: pip install pyinstaller") + return False + + # Проверка иконки (не обязательна) + icon_paths = ['icon.ico', 'assets/icon.ico', 'images/icon.ico'] + self.icon_file = None + + for icon in icon_paths: + if Path(icon).exists(): + self.icon_file = icon + print(f"✓ Иконка найдена: {icon}") + break + + if not self.icon_file: + print("⚠️ Иконка не найдена. Будет использована стандартная иконка.") + + # Проверка основного файла + main_files = ['app.py'] + self.main_file = None + + for main in main_files: + if Path(main).exists(): + self.main_file = main + print(f"✓ Основной файл: {main}") + break + + if not self.main_file: + print("✗ Основной файл не найден!") + print("Создайте main.py или укажите другой файл через --main") + return False + + return True + + def setup_environment(self): + """Настройка окружения для 32-битной сборки""" + print("\n⚙️ Настройка окружения...") + + # Установка переменных окружения для Windows + if platform.system() == 'Windows': + os.environ['PYINSTALLER_FORCE_32BIT'] = '1' + print("✓ Установлена переменная PYINSTALLER_FORCE_32BIT=1") + + # Создание папок если нужно + self.dist_dir.mkdir(exist_ok=True) + print(f"✓ Папка dist готова") + + return True + + def generate_version_info(self): + """Генерация файла с информацией о версии""" + version_content = f"""# UTF-8 +VSVersionInfo( + ffi=FixedFileInfo( + filevers=({self.version_major}, {self.version_minor}, {self.version_patch}, {self.version_build}), + prodvers=({self.version_major}, {self.version_minor}, {self.version_patch}, {self.version_build}), + mask=0x3f, + flags=0x0, + OS=0x40004, + fileType=0x1, + subtype=0x0, + date=(0, 0) + ), + kids=[ + StringFileInfo([ + StringTable( + '040904B0', + [ + StringStruct('CompanyName', '{self.company_name}'), + StringStruct('FileDescription', '{self.file_description}'), + StringStruct('FileVersion', '{self.version_major}.{self.version_minor}.{self.version_patch}.{self.version_build}'), + StringStruct('InternalName', '{self.app_name}'), + StringStruct('LegalCopyright', '{self.copyright}'), + StringStruct('OriginalFilename', '{self.app_name}.exe'), + StringStruct('ProductName', '{self.product_name}'), + StringStruct('ProductVersion', '{self.version_major}.{self.version_minor}.{self.version_patch}'), + ]) + ]), + VarFileInfo([VarStruct('Translation', [1033, 1200])]) + ] +) +""" + + version_file = Path('version_info.txt') + with open(version_file, 'w', encoding='utf-8-sig') as f: + f.write(version_content) + + print("✓ Создан version_info.txt") + return version_file + + def build_with_pyinstaller(self, clean_build=True): + """Сборка с помощью PyInstaller""" + print(f"\n🔨 Сборка приложения...") + + # Очистка предыдущих сборок + if clean_build: + if self.build_dir.exists(): + shutil.rmtree(self.build_dir, ignore_errors=True) + print("✓ Очищена папка build") + + # Команда сборки + cmd = [ + sys.executable, '-m', 'PyInstaller', + '--onefile', # Один файл + '--clean', # Очистка кэша + '--noconfirm', # Не спрашивать подтверждение + ] + + # Добавление иконки если есть + if self.icon_file and Path(self.icon_file).exists(): + cmd.extend(['--icon', self.icon_file]) + print(f"✓ Используется иконка: {self.icon_file}") + else: + print("⚠️ Иконка не используется") + + # Добавление информации о версии (только Windows) + version_file = Path('version_info.txt') + if platform.system() == 'Windows' and version_file.exists(): + cmd.extend(['--version-file', 'version_info.txt']) + print("✓ Используется информация о версии") + + # Имя приложения + cmd.extend(['--name', self.app_name]) + + # Скрыть консоль для GUI приложений + if not self.show_console: + cmd.append('--noconsole') + cmd.append('--windowed') + + # Скрытые импорты + for imp in self.hidden_imports: + cmd.extend(['--hidden-import', imp]) + + # Добавление данных + for data_src, data_dst in self.data_files: + cmd.extend(['--add-data', f'{data_src}{os.pathsep}{data_dst}']) + + # Добавление основного файла + cmd.append(str(self.main_file)) + + # Вывод команды + print(f"Команда: {' '.join(cmd)}") + print("\n🔄 Запуск PyInstaller...") + + # Выполнение с правильной кодировкой для Windows + try: + # Для Windows используем кодировку cp1251 или текущую системную + encoding = 'cp866' if platform.system() == 'Windows' else 'utf-8' + + result = subprocess.run( + cmd, + check=True, + capture_output=True, + text=True, + encoding=encoding, + errors='replace' # Игнорировать ошибки декодирования + ) + + print("\n✅ Сборка завершена успешно!") + + if result.stdout and result.stdout.strip(): + print(f"\nВывод PyInstaller:\n{result.stdout}") + + if result.stderr and result.stderr.strip(): + print(f"\nПредупреждения PyInstaller:\n{result.stderr}") + + return True + + except subprocess.CalledProcessError as e: + print(f"\n❌ Ошибка сборки!") + print(f"Код ошибки: {e.returncode}") + + # Вывод с правильной кодировкой + if e.stdout: + print(f"\nВывод PyInstaller:\n{e.stdout}") + if e.stderr: + print(f"\nОшибки PyInstaller:\n{e.stderr}") + + return False + except Exception as e: + print(f"\n❌ Неожиданная ошибка: {e}") + import traceback + traceback.print_exc() + return False + + def build_with_pyinstaller_simple(self): + """Упрощенный метод сборки без перехвата вывода""" + print(f"\n🔨 Сборка приложения (упрощенный метод)...") + + # Очистка предыдущих сборок + if self.build_dir.exists(): + shutil.rmtree(self.build_dir, ignore_errors=True) + print("✓ Очищена папка build") + + # Базовые аргументы + args = [ + sys.executable, '-m', 'PyInstaller', + '--onefile', + '--clean', + '--noconfirm', + '--name', self.app_name, + ] + + # Дополнительные аргументы + if not self.show_console: + args.extend(['--noconsole', '--windowed']) + + if self.icon_file and Path(self.icon_file).exists(): + args.extend(['--icon', self.icon_file]) + + version_file = Path('version_info.txt') + if platform.system() == 'Windows' and version_file.exists(): + args.extend(['--version-file', 'version_info.txt']) + + # Добавить main.py + args.append(str(self.main_file)) + + print(f"Запуск: {' '.join(args)}") + + # Прямой запуск + try: + return_code = subprocess.call(args) + if return_code == 0: + print("\n✅ Сборка завершена успешно!") + return True + else: + print(f"\n❌ Ошибка сборки. Код возврата: {return_code}") + return False + except Exception as e: + print(f"\n❌ Ошибка: {e}") + return False + + def optimize_with_upx(self): + """Оптимизация с помощью UPX""" + print("\n⚡ Оптимизация с UPX...") + + exe_file = self.dist_dir / f'{self.app_name}.exe' + + if not exe_file.exists(): + print("✗ Исполняемый файл не найден") + return False + + # Проверка наличия UPX + try: + subprocess.run(['upx', '--version'], + capture_output=True, check=True, + shell=True) # shell=True для Windows + print("✓ UPX найден") + except (subprocess.CalledProcessError, FileNotFoundError): + print("⚠️ UPX не установлен. Пропуск оптимизации.") + print("Скачайте UPX: https://upx.github.io/") + return True + + # Оптимизация + original_size = exe_file.stat().st_size / (1024 * 1024) + + cmd = ['upx', '--best', '--lzma', str(exe_file)] + + try: + result = subprocess.run(cmd, check=True, capture_output=True, shell=True) + + new_size = exe_file.stat().st_size / (1024 * 1024) + reduction = ((original_size - new_size) / original_size) * 100 + + print(f"✓ Оптимизация завершена!") + print(f"Размер: {original_size:.2f} MB → {new_size:.2f} MB") + print(f"Сжатие: {reduction:.1f}%") + + return True + + except subprocess.CalledProcessError as e: + print(f"⚠️ Ошибка оптимизации UPX: {e}") + if e.stderr: + print(f"Ошибка: {e.stderr.decode('utf-8', errors='replace')}") + return False + + def test_executable(self): + """Тестирование исполняемого файла""" + print("\n🧪 Тестирование приложения...") + + exe_path = self.dist_dir / f'{self.app_name}.exe' + + if not exe_path.exists(): + # Проверим другие возможные расширения + for ext in ['.exe', '']: + test_path = self.dist_dir / f'{self.app_name}{ext}' + if test_path.exists(): + exe_path = test_path + break + + if not exe_path.exists(): + print("✗ Исполняемый файл не найден") + print(f"Искали в: {self.dist_dir}") + return False + + size_mb = exe_path.stat().st_size / (1024 * 1024) + print(f"✓ Файл создан: {exe_path.name}") + print(f" Размер: {size_mb:.2f} MB") + print(f" Путь: {exe_path}") + + # Проверка архитектуры через pefile если доступен + try: + import pefile + pe = pefile.PE(str(exe_path)) + machine = pe.FILE_HEADER.Machine + arch_map = { + 0x14c: '32-bit (x86)', + 0x8664: '64-bit (x64)', + } + arch_text = arch_map.get(machine, f'Unknown ({hex(machine)})') + print(f" Архитектура: {arch_text}") + print(f" Секций: {pe.FILE_HEADER.NumberOfSections}") + except ImportError: + print(" ℹ️ Установите pefile для детальной информации: pip install pefile") + except Exception as e: + print(f" ℹ️ Не удалось проверить архитектуру: {e}") + + # Спросить о запуске теста + if self.auto_test: + response = 'y' + else: + response = input("\nЗапустить приложение для теста? (y/n): ") + + if response.lower() == 'y': + print(f"Запуск {exe_path.name}...") + try: + subprocess.Popen([str(exe_path)], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + print("✓ Приложение запущено") + except Exception as e: + print(f"⚠️ Ошибка запуска: {e}") + + return True + + def cleanup(self): + """Очистка временных файлов""" + print("\n🧹 Очистка временных файлов...") + + # Удаляем только временные файлы, созданные скриптом + temp_files = [ + 'version_info.txt', + f'{self.app_name}.spec', + ] + + for temp_file in temp_files: + temp_path = Path(temp_file) + if temp_path.exists(): + try: + temp_path.unlink() + print(f"✓ Удален {temp_file}") + except Exception as e: + print(f"⚠️ Не удалось удалить {temp_file}: {e}") + + print("✓ Очистка завершена") + + def build(self, args): + """Основной процесс сборки""" + self.print_banner() + + # Настройка параметров + self.app_name = args.name or 'MyApp' + self.company_name = args.company or 'My Company' + self.product_name = args.product or self.app_name + self.file_description = args.description or f'{self.app_name} Application' + self.copyright = args.copyright or f'Copyright © 2024 {self.company_name}' + + self.version_major = args.version_major or 1 + self.version_minor = args.version_minor or 0 + self.version_patch = args.version_patch or 0 + self.version_build = args.version_build or 0 + + self.show_console = args.console + self.auto_test = args.test + self.hidden_imports = args.hidden_import or [] + self.data_files = [] + + # Парсинг файлов данных + if args.add_data: + for data in args.add_data: + if ':' in data: + src, dst = data.split(':', 1) + self.data_files.append((src, dst)) + else: + print(f"⚠️ Неверный формат данных: {data}. Используйте src:dst") + + # Проверка требований + if not self.check_requirements(): + return False + + # Настройка окружения + if not self.setup_environment(): + return False + + # Генерация информации о версии + if platform.system() == 'Windows': + self.generate_version_info() + + # Сборка (используем упрощенный метод) + print("\n" + "=" * 50) + print("Выберите метод сборки:") + print("1. Упрощенный (рекомендуется)") + print("2. С перехватом вывода (может быть проблема с кодировкой)") + print("=" * 50) + + choice = input("Ваш выбор (1 или 2): ").strip() + + success = False + if choice == '1': + success = self.build_with_pyinstaller_simple() + elif choice == '2': + success = self.build_with_pyinstaller(clean_build=args.clean) + else: + print("Используется метод по умолчанию (упрощенный)") + success = self.build_with_pyinstaller_simple() + + if not success: + print("\n❌ Сборка не удалась.") + return False + + # Оптимизация UPX + if args.upx: + self.optimize_with_upx() + + # Тестирование + self.test_executable() + + # Очистка + if args.cleanup: + self.cleanup() + + print("\n" + "=" * 60) + print("🎉 СБОРКА УСПЕШНО ЗАВЕРШЕНА!") + print("=" * 60) + print(f"\n📦 Ваше приложение готово:") + print(f" Файл: dist/{self.app_name}.exe") + print(f" Архитектура: 32-битная") + print(f" Тип: Один исполняемый файл") + print(f" Иконка: {'Да' if self.icon_file else 'Нет'}") + + print("\n📋 Следующие шаги:") + print("1. Протестируйте приложение на целевой системе") + print("2. Используйте Inno Setup для создания установщика") + print("3. Подпишите код для Windows (опционально)") + + return True + + +def setup_logging(): + """Настраивает логирование в файл сессии программы.""" + session_time = datetime.now().strftime('%Y-%m-%d_%H-%M-%S') + log_filename = f"session_{session_time}.log" + logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s', + handlers=[ + logging.FileHandler(log_filename, encoding='utf-8'), + logging.StreamHandler() + ] + ) + + +def main(): + """Точка входа""" + parser = argparse.ArgumentParser( + description='Сборка 32-битного приложения в один файл', + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Примеры использования: + python build.py + python build.py --name "MyApp" --console + python build.py --add-data "data:data" --add-data "config.ini:." + python build.py --version-major 2 --version-minor 1 + """ + ) + + # Основные параметры + parser.add_argument('--name', help='Имя приложения (по умолчанию: MyApp)') + parser.add_argument('--main', help='Основной файл Python (по умолчанию: main.py)') + + # Версия + parser.add_argument('--version-major', type=int, help='Основная версия') + parser.add_argument('--version-minor', type=int, help='Дополнительная версия') + parser.add_argument('--version-patch', type=int, help='Патч версия') + parser.add_argument('--version-build', type=int, help='Номер сборки') + + # Информация о приложении + parser.add_argument('--company', help='Название компании') + parser.add_argument('--product', help='Название продукта') + parser.add_argument('--description', help='Описание файла') + parser.add_argument('--copyright', help='Текст авторских прав') + + # Опции сборки + parser.add_argument('--console', action='store_true', + help='Показать консоль') + parser.add_argument('--no-upx', dest='upx', action='store_false', default=True, + help='Не использовать UPX для сжатия') + parser.add_argument('--no-clean', dest='clean', action='store_false', default=True, + help='Не очищать папку build перед сборкой') + + # Данные и импорты + parser.add_argument('--add-data', action='append', + help='Добавить файлы данных (формат: src:dst)') + parser.add_argument('--hidden-import', action='append', + help='Добавить скрытый импорт модуля') + + # Тестирование и очистка + parser.add_argument('--test', action='store_true', + help='Автоматически протестировать после сборки') + parser.add_argument('--no-cleanup', dest='cleanup', action='store_false', default=True, + help='Не удалять временные файлы') + + args = parser.parse_args() + + # Создание и запуск сборщика + builder = OneFileBuilder() + + try: + success = builder.build(args) + sys.exit(0 if success else 1) + except KeyboardInterrupt: + print("\n\n⚠️ Сборка прервана пользователем") + sys.exit(130) + except Exception as e: + print(f"\n❌ Неожиданная ошибка: {e}") + import traceback + traceback.print_exc() + sys.exit(1) + + +if __name__ == '__main__': + # Вызов настройки логирования при запуске программы + setup_logging() + + main() \ No newline at end of file diff --git a/icon.ico b/icon.ico new file mode 100644 index 0000000..5d09831 Binary files /dev/null and b/icon.ico differ diff --git a/services/browser_service.py b/services/browser_service.py index cfa5b4e..b1197a8 100644 --- a/services/browser_service.py +++ b/services/browser_service.py @@ -360,21 +360,86 @@ class BrowserService: def close_current_tab(self): """Закрывает текущую вкладку и переходит на предыдущую.""" try: - if len(self.driver.window_handles) > 1: - logging.info("📑 Закрываю текущую вкладку...") - self.driver.close() - # Переходим на последнюю оставшуюся вкладку - self.driver.switch_to.window(self.driver.window_handles[-1]) - time.sleep(1) - logging.info("✅ Перешел на предыдущую вкладку.") - return True - else: - logging.warning("⚠️ Это последняя вкладка, не закрываю.") - return False + logging.info("📑 Метод close_current_tab вызван, но вкладки не будут закрываться.") + # Удаляем логику закрытия вкладок + return True except Exception as e: - logging.error(f"❌ Ошибка при закрытии вкладки: {e}") + logging.error(f"❌ Ошибка при попытке закрыть вкладку: {e}") return False def get_tabs_count(self): """Возвращает количество открытых вкладок.""" return len(self.driver.window_handles) + + def select_tab_by_name(self, tab_name): + """Выбирает вкладку на странице по её названию.""" + try: + logging.info(f"🔍 Поиск вкладки с названием: {tab_name}") + # Ищем элемент вкладки по тексту + tab_element = self.wait.until( + EC.presence_of_element_located((By.XPATH, f"//a[text()='{tab_name}']")) + ) + tab_element.click() + logging.info(f"✅ Вкладка '{tab_name}' выбрана.") + return True + except Exception as e: + logging.error(f"❌ Ошибка при выборе вкладки '{tab_name}': {e}") + return False + + def select_tab_by_text_or_name(self, target_text, timeout=30): + """Ищет и выбирает вкладку по тексту или названию, делая повторные попытки. + + Args: + target_text (str): Текст или название вкладки для поиска. + timeout (int): Общее время ожидания в секундах. + + Returns: + bool: True, если вкладка найдена и выбрана, иначе False. + """ + end_time = time.time() + timeout + while time.time() < end_time: + try: + logging.info(f"🔍 Поиск вкладки с текстом или названием: {target_text}") + # Ищем элемент вкладки по тексту + tab_element = self.wait.until( + EC.presence_of_element_located((By.XPATH, f"//*[text()='{target_text}']")) + ) + self.driver.execute_script("arguments[0].scrollIntoView(true);", tab_element) + time.sleep(0.5) + tab_element.click() + logging.info(f"✅ Вкладка с текстом '{target_text}' выбрана.") + return True + except Exception as e: + logging.warning(f"⚠️ Вкладка с текстом '{target_text}' не найдена, повторная попытка...") + time.sleep(1) # Ждем перед повторной попыткой + logging.error(f"❌ Не удалось найти вкладку с текстом '{target_text}' за {timeout} секунд.") + return False + + def set_tabs(self, tabs): + """Устанавливает массив с названиями вкладок.""" + self.tabs = tabs + + def select_tab_from_list(self, tab_index): + """Выбирает вкладку из массива по индексу. + + Args: + tab_index (int): Индекс вкладки в массиве. + + Returns: + bool: True, если вкладка выбрана успешно, иначе False. + """ + try: + if not hasattr(self, 'tabs') or not self.tabs: + logging.error("❌ Массив вкладок не установлен или пуст.") + return False + + if tab_index < 0 or tab_index >= len(self.tabs): + logging.error("❌ Индекс вкладки выходит за пределы массива.") + return False + + tab_name = self.tabs[tab_index] + logging.info(f"🔍 Выбор вкладки с индексом {tab_index}: {tab_name}") + return self.select_tab_by_name(tab_name) + except Exception as e: + logging.error(f"❌ Ошибка при выборе вкладки по индексу {tab_index}: {e}") + return False diff --git a/test.xlsx b/test.xlsx new file mode 100644 index 0000000..e69de29 diff --git a/test1.xlsx b/test1.xlsx new file mode 100644 index 0000000..e69de29 diff --git a/test2.xlsx b/test2.xlsx new file mode 100644 index 0000000..e69de29 diff --git a/test3.xlsx b/test3.xlsx new file mode 100644 index 0000000..e69de29 diff --git a/test5.xlsx b/test5.xlsx new file mode 100644 index 0000000..e69de29 diff --git a/tset4.xlsx b/tset4.xlsx new file mode 100644 index 0000000..e69de29 diff --git a/ui.py b/ui.py index 6ed700f..6ab68c5 100644 --- a/ui.py +++ b/ui.py @@ -7,6 +7,7 @@ import threading import logging from pathlib import Path from services.processor import FileProcessorService +from services.browser_service import BrowserService class UIManager: @@ -16,7 +17,7 @@ class UIManager: """Инициализирует интерфейс.""" self.root = root self.root.title("🔄 Автоматизация загрузки файлов") - self.root.geometry("700x600") + self.root.geometry("700x800") self.root.configure(bg="#2C3E50") self.root.minsize(500, 400) @@ -92,9 +93,23 @@ class UIManager: ) url_label.pack(anchor="w", pady=(10, 5)) - self.url_entry = tk.Entry(main_frame, font=self.font_label) - self.url_entry.pack(anchor="w", fill=tk.X, pady=(0, 15)) - self.url_entry.insert(0, "https://") + # Кнопка Вставить рядом с полем URL + url_frame = tk.Frame(main_frame, bg="#2C3E50") + url_frame.pack(fill=tk.X, pady=(0, 15)) + + self.url_entry = tk.Entry(url_frame, font=self.font_label) + self.url_entry.pack(side="left", fill=tk.X, expand=True) + + insert_button = tk.Button( + url_frame, + text="Вставить", + command=self._insert_from_clipboard, + font=self.font_button, + bg="#27AE60", + fg="white", + padx=10 + ) + insert_button.pack(side="left", padx=(10, 0)) # ===== ПОЛЕ 3: ВАРИАНТЫ КНОПОК ===== buttons_label = tk.Label( @@ -190,6 +205,19 @@ class UIManager: # Перенаправление логов в текстовое поле self._setup_log_handler() + + # ===== ВЫБОР ВКЛАДКИ ===== + tabs_label = tk.Label( + main_frame, + text="📑 Выбор вкладки браузера:", + font=self.font_label, + bg="#2C3E50", + fg="#ECF0F1" + ) + tabs_label.pack(anchor="w", pady=(10, 5)) + + self.tab_selector = TabSelectorApp(main_frame, BrowserService(download_dir="./downloads")) + self.tab_selector.pack(fill=tk.X, pady=(0, 15)) def _setup_log_handler(self): """Настраивает обработчик логов в текстовое поле.""" @@ -273,6 +301,15 @@ class UIManager: # Создаем процессор processor = FileProcessorService(directory, url, download_dir) + # Проверка вкладок перед началом загрузки + browser_service = BrowserService(download_dir="./downloads") + browser_service.set_tabs(["Вкладка 1", "Вкладка 2", "Вкладка 3"]) + + # Пример: выбор вкладки по индексу + if not browser_service.select_tab_from_list(0): + logging.error("❌ Не удалось выбрать вкладку. Проверьте массив вкладок.") + return + # Запускаем обработку results = processor.start_processing(upload_texts, process_texts) @@ -310,6 +347,53 @@ class UIManager: messagebox.showinfo("Результаты", result_text) + def _insert_from_clipboard(self): + """Обработчик для вставки текста из буфера обмена.""" + try: + clipboard_text = self.root.clipboard_get() + self.url_entry.delete(0, tk.END) # Очистить поле + self.url_entry.insert(0, clipboard_text) # Вставить текст из буфера + except tk.TclError: + messagebox.showerror("Ошибка", "Буфер обмена пуст или недоступен.") + + +class TabSelectorApp: + def __init__(self, parent, browser_service): + self.browser_service = browser_service + self.frame = tk.Frame(parent, bg="#2C3E50") + + # Выпадающий список + self.combo = ttk.Combobox(self.frame, state="readonly") + self.combo.pack(pady=10) + + # Кнопка для выбора вкладки + self.select_button = tk.Button(self.frame, text="Выбрать вкладку", command=self.select_tab) + self.select_button.pack(pady=10) + + def pack(self, **kwargs): + """Упрощает упаковку фрейма.""" + self.frame.pack(**kwargs) + + def set_tabs(self, tabs): + """Устанавливает массив вкладок и обновляет выпадающий список.""" + self.tabs = tabs + self.combo["values"] = self.tabs + if self.tabs: + self.combo.current(0) # Устанавливаем первый элемент по умолчанию + + def select_tab(self): + """Выбирает вкладку из выпадающего списка.""" + selected_index = self.combo.current() + if selected_index == -1: + print("❌ Вкладка не выбрана.") + return + + success = self.browser_service.select_tab_from_list(selected_index) + if success: + print(f"✅ Вкладка '{self.tabs[selected_index]}' выбрана.") + else: + print(f"❌ Не удалось выбрать вкладку '{self.tabs[selected_index]}'." ) + def main(): """Главная функция."""