This commit is contained in:
unknown
2026-02-09 14:28:39 +05:00
parent 5fd5de3baf
commit be7aadf218
10 changed files with 754 additions and 16 deletions

589
build.py Normal file
View File

@@ -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()

BIN
icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

View File

@@ -360,21 +360,86 @@ class BrowserService:
def close_current_tab(self): def close_current_tab(self):
"""Закрывает текущую вкладку и переходит на предыдущую.""" """Закрывает текущую вкладку и переходит на предыдущую."""
try: try:
if len(self.driver.window_handles) > 1: logging.info("📑 Метод close_current_tab вызван, но вкладки не будут закрываться.")
logging.info("📑 Закрываю текущую вкладку...") # Удаляем логику закрытия вкладок
self.driver.close() return True
# Переходим на последнюю оставшуюся вкладку
self.driver.switch_to.window(self.driver.window_handles[-1])
time.sleep(1)
logging.info("✅ Перешел на предыдущую вкладку.")
return True
else:
logging.warning("⚠️ Это последняя вкладка, не закрываю.")
return False
except Exception as e: except Exception as e:
logging.error(f"❌ Ошибка при закрытии вкладки: {e}") logging.error(f"❌ Ошибка при попытке закрыть вкладку: {e}")
return False return False
def get_tabs_count(self): def get_tabs_count(self):
"""Возвращает количество открытых вкладок.""" """Возвращает количество открытых вкладок."""
return len(self.driver.window_handles) 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

0
test.xlsx Normal file
View File

0
test1.xlsx Normal file
View File

0
test2.xlsx Normal file
View File

0
test3.xlsx Normal file
View File

0
test5.xlsx Normal file
View File

0
tset4.xlsx Normal file
View File

92
ui.py
View File

@@ -7,6 +7,7 @@ import threading
import logging import logging
from pathlib import Path from pathlib import Path
from services.processor import FileProcessorService from services.processor import FileProcessorService
from services.browser_service import BrowserService
class UIManager: class UIManager:
@@ -16,7 +17,7 @@ class UIManager:
"""Инициализирует интерфейс.""" """Инициализирует интерфейс."""
self.root = root self.root = root
self.root.title("🔄 Автоматизация загрузки файлов") self.root.title("🔄 Автоматизация загрузки файлов")
self.root.geometry("700x600") self.root.geometry("700x800")
self.root.configure(bg="#2C3E50") self.root.configure(bg="#2C3E50")
self.root.minsize(500, 400) self.root.minsize(500, 400)
@@ -92,9 +93,23 @@ class UIManager:
) )
url_label.pack(anchor="w", pady=(10, 5)) url_label.pack(anchor="w", pady=(10, 5))
self.url_entry = tk.Entry(main_frame, font=self.font_label) # Кнопка Вставить рядом с полем URL
self.url_entry.pack(anchor="w", fill=tk.X, pady=(0, 15)) url_frame = tk.Frame(main_frame, bg="#2C3E50")
self.url_entry.insert(0, "https://") 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: ВАРИАНТЫ КНОПОК ===== # ===== ПОЛЕ 3: ВАРИАНТЫ КНОПОК =====
buttons_label = tk.Label( buttons_label = tk.Label(
@@ -190,6 +205,19 @@ class UIManager:
# Перенаправление логов в текстовое поле # Перенаправление логов в текстовое поле
self._setup_log_handler() 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): def _setup_log_handler(self):
"""Настраивает обработчик логов в текстовое поле.""" """Настраивает обработчик логов в текстовое поле."""
@@ -273,6 +301,15 @@ class UIManager:
# Создаем процессор # Создаем процессор
processor = FileProcessorService(directory, url, download_dir) 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) results = processor.start_processing(upload_texts, process_texts)
@@ -310,6 +347,53 @@ class UIManager:
messagebox.showinfo("Результаты", result_text) 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(): def main():
"""Главная функция.""" """Главная функция."""