также нужно, чтобы выделенные прокси (при помощи Ctrl+A или просто несколько штук при помощи левой кнопки мыши) - копировались в буфер обмена на сочетании клавиш Ctrl+C.
а также, чтобы можно было вставлять прокси в список не только через контекстное меню списка "Вставить прокси из буфера обмена", но и при нажатию Ctrl+V

вот код, и дай полный код:
import sys
import os
import json
import requests
import re
import pycountry
from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QLabel, QLineEdit, QPushButton, QCheckBox,
    QListWidget, QVBoxLayout, QHBoxLayout, QSystemTrayIcon, QMenu, QDialog,
    QSpacerItem, QSizePolicy, QWidget, QFileDialog, QInputDialog, QButtonGroup, QRadioButton
)
from PyQt6.QtGui import QIcon, QAction, QDesktopServices, QCursor, QPixmap
from PyQt6.QtCore import Qt, QUrl, QTimer, pyqtSignal
from cryptography.fernet import Fernet

# Путь к файлам настроек и иконкам
PROXY_FILE = 'D:\\ip_info\\settings\\proxies.json'
SETTINGS_FILE = 'D:\\ip_info\\settings\\settings.json'
ICON_PATH = 'D:\\ip_info\\icons\\app_ip.ico'
CRYPT_DIR = 'D:\\ip_info\\settings\\cryptography'
CRYPT_KEY_FILE = os.path.join(CRYPT_DIR, 'key.key')
ERROR_ICON_PATH = 'D:\\ip_info\\icons\\error_ip.png'
LOGO_PATH = 'D:\\ip_info\\icons\\logo.jpg'

# Стиль для кнопок
BUTTON_STYLE = """
    QPushButton {
        border: none;
        background-color: #4CAF50;
        color: white;
        padding: 10px 20px;
        border-radius: 5px;
    }
    QPushButton:hover {
        background-color: #45a049;
    }
"""

BUTTON_STYLE_DELETE = """
    QPushButton {
        border: none;
        background-color: #f44336;
        color: white;
        padding: 10px 20px;
        border-radius: 5px;
    }
    QPushButton:hover {
        background-color: #da190b;
    }
"""

CHECKBOX_STYLE = """
    QCheckBox {
        color: white;
    }
    QCheckBox:disabled {
        color: #7f7f7f;
    }
"""

def get_ip_and_isp(proxy_data=None, use_proxy=False, service_url='https://ipinfo.io/json'):
    try:
        proxies = None
        if use_proxy and proxy_data:
            protocol = proxy_data.get('protocol', 'socks5')
            server_port = proxy_data['server_port']
            username = proxy_data.get('username')
            password = proxy_data.get('password')
            proxy_url = f"{protocol}://{server_port}"
            if username and password:
                proxy_url = f"{protocol}://{username}:{password}@{server_port}"
            proxies = {
                'http': proxy_url,
                'https': proxy_url,
            }
        response = requests.get(service_url, proxies=proxies, timeout=10)
        response.raise_for_status()  # Raise an error for bad responses
        data = response.json()

        if 'ipinfo.io' in service_url:
            ip = data.get('ip', 'N/A')
            org = data.get('org', 'N/A')
            city = data.get('city', 'N/A')
            country = data.get('country', 'N/A')

            if org.startswith('AS'):
                isp = ' '.join(org.split(' ')[1:])
            else:
                isp = org

        elif 'ipapi.co' in service_url:
            ip = data.get('ip', 'N/A')
            isp = data.get('org', 'N/A')
            city = data.get('city', 'N/A')
            country = data.get('country_name', 'N/A')

            if isp.startswith('AS'):
                isp = ' '.join(isp.split(' ')[1:])

        elif 'ip-api.com' in service_url:
            if data.get('status') != 'success':
                raise ValueError('API response status not success')
            ip = data.get('query', 'N/A')
            isp = data.get('isp', 'N/A')
            city = data.get('city', 'N/A')
            country = data.get('country', 'N/A')

        else:
            ip = 'N/A'
            isp = 'N/A'
            city = 'N/A'
            country = 'N/A'

        legal_forms = ['PJSC', 'LLC', 'JSC', 'OJSC', 'АО', 'ОАО', 'ЗАО', 'ООО', 'ПАО']
        pattern = r'\b(' + '|'.join(legal_forms) + r')\b'
        isp = re.sub(pattern, '', isp, flags=re.IGNORECASE).strip()
        isp = isp.replace('"', '').replace('«', '').replace('»', '').strip()
        isp = ' '.join(isp.split())

        return ip, isp, city, country
    except Exception as e:
        print(f"Error in get_ip_and_isp: {e}")
        return 'Ошибка', 'Ошибка', 'Ошибка', 'Ошибка'

class AboutDialog(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle('О программе')
        self.setFixedSize(400, 200)
        self.setWindowIcon(parent.icon if parent and hasattr(parent, 'icon') else QIcon())

        layout = QHBoxLayout()

        # Левая часть с логотипом
        logo_layout = QVBoxLayout()
        logo_label = QLabel()
        if os.path.exists(LOGO_PATH):
            logo_pixmap = QPixmap(LOGO_PATH).scaledToWidth(200, Qt.TransformationMode.SmoothTransformation)
            logo_label.setPixmap(logo_pixmap)
            logo_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        logo_layout.addWidget(logo_label)

        # Правая часть с информацией
        info_layout = QVBoxLayout()
        
        title_label = QLabel("JustWoofIP")
        title_font = title_label.font()
        title_font.setPointSize(14)
        title_font.setBold(True)
        title_label.setFont(title_font)
        title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        info_layout.addWidget(title_label)

        version_label = QLabel("Версия: 1.0.0-20241101")
        version_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        info_layout.addWidget(version_label)

        site_label = QLabel()
        site_label.setText(
            'Сайт: <a href="https://chat.mistral.ai" style="color: #1E90FF; font-weight: bold; text-decoration: none;">https://chat.mistral.ai</a>'
        )
        site_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        site_label.setOpenExternalLinks(False)
        site_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextBrowserInteraction)
        site_label.linkActivated.connect(self.open_link)
        site_label.setStyleSheet("""
            QLabel {
                color: white;
                background-color: #1e1e1e;
            }
            a:hover {
                text-decoration: underline;
            }
        """)
        info_layout.addWidget(site_label)

        close_button = QPushButton("Закрыть")
        close_button.clicked.connect(self.close)
        close_button.setStyleSheet(BUTTON_STYLE)
        close_button.setFixedWidth(100)
        close_button.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
        info_layout.addWidget(close_button, alignment=Qt.AlignmentFlag.AlignCenter)

        layout.addLayout(logo_layout)
        layout.addLayout(info_layout)
        self.setLayout(layout)
        self.setStyleSheet("background-color: #1e1e1e; color: white;")

    def open_link(self, link):
        QDesktopServices.openUrl(QUrl(link))
        self.close()

class ClickableLabel(QLabel):
    clicked = pyqtSignal()

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))

    def mousePressEvent(self, event):
        if event.button() == Qt.MouseButton.LeftButton:
            self.clicked.emit()
        super().mousePressEvent(event)

class ProxyListWidget(QListWidget):
    deleteRequested = pyqtSignal()
    editRequested = pyqtSignal()
    insertFromClipboardRequested = pyqtSignal()

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
        self.customContextMenuRequested.connect(self.open_context_menu)

    def keyPressEvent(self, event):
        if event.key() == Qt.Key.Key_Delete:
            self.deleteRequested.emit()
        else:
            super().keyPressEvent(event)

    def open_context_menu(self, position):
        context_menu = QMenu(self)
        edit_action = QAction("Редактировать прокси", self)
        edit_action.triggered.connect(self.editRequested.emit)
        context_menu.addAction(edit_action)

        insert_action = QAction("Вставить прокси из буфера обмена", self)
        insert_action.triggered.connect(self.insertFromClipboardRequested.emit)
        context_menu.addAction(insert_action)
        
        context_menu.exec(self.viewport().mapToGlobal(position))

class MyApp(QMainWindow):
    def __init__(self):
        super().__init__()

        # Инициализация переменных для Undo/Redo
        self.minimize_to_tray = False
        self.current_service_url = None
        self.deleted_proxies_stack = []  # Стек для Undo удаления
        self.redo_deleted_proxies_stack = []  # Стек для Redo удаления
        self.copy_history_stack = []  # Стек для Undo копирования
        self.redo_copy_history_stack = []  # Стек для Redo копирования

        self.load_or_generate_key()
        self.initUI()

    def load_or_generate_key(self):
        if not os.path.exists(CRYPT_KEY_FILE):
            key = Fernet.generate_key()
            os.makedirs(os.path.dirname(CRYPT_KEY_FILE), exist_ok=True)
            with open(CRYPT_KEY_FILE, 'wb') as key_file:
                key_file.write(key)
        else:
            with open(CRYPT_KEY_FILE, 'rb') as key_file:
                key = key_file.read()
        self.fernet = Fernet(key)

    def initUI(self):
        self.setWindowTitle('JustWoofIP')
        self.setStyleSheet("background-color: #1e1e1e; color: white;")
        if os.path.exists(ICON_PATH):
            self.icon = QIcon(ICON_PATH)
            self.setWindowIcon(self.icon)
        else:
            self.icon = QIcon()

        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        layout = QVBoxLayout()
        central_widget.setLayout(layout)

        self.create_menu()

        # IP Section
        hbox_ip = QHBoxLayout()
        ip_label = QLabel("Мой IP:")
        self.ip_text = QLineEdit()
        self.ip_text.setReadOnly(True)
        self.ip_text.setStyleSheet("background-color: #323232; color: white;")
        hbox_ip.addWidget(ip_label)
        hbox_ip.addWidget(self.ip_text)

        self.copy_ip_button = QPushButton("Копировать")
        self.copy_ip_button.setStyleSheet(BUTTON_STYLE)
        self.copy_ip_button.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
        self.copy_ip_button.clicked.connect(self.copy_ip)  # Подключение после определения кнопки
        hbox_ip.addWidget(self.copy_ip_button)
        layout.addLayout(hbox_ip)

        # ISP, City, Country Section
        hbox_isp = QHBoxLayout()

        isp_label = QLabel("Мой ISP:")
        self.isp_text = QLineEdit()
        self.isp_text.setReadOnly(True)
        self.isp_text.setStyleSheet("background-color: #323232; color: white;")
        hbox_isp.addWidget(isp_label)
        hbox_isp.addWidget(self.isp_text)

        city_label = QLabel("Город:")
        self.city_text = QLineEdit()
        self.city_text.setReadOnly(True)
        self.city_text.setStyleSheet("background-color: #323232; color: white;")
        hbox_isp.addWidget(city_label)
        hbox_isp.addWidget(self.city_text)

        country_label = QLabel("Страна:")
        self.country_text = QLineEdit()
        self.country_text.setReadOnly(True)
        self.country_text.setStyleSheet("background-color: #323232; color: white;")
        hbox_isp.addWidget(country_label)
        hbox_isp.addWidget(self.country_text)

        self.flag_label = ClickableLabel()
        self.flag_label.setFixedSize(32, 24)
        self.flag_label.clicked.connect(self.open_service_url)
        hbox_isp.addWidget(self.flag_label)

        layout.addLayout(hbox_isp)

        # Use Proxy Section
        hbox_use_proxy = QHBoxLayout()
        self.use_proxy_checkbox = QCheckBox("Использовать прокси")
        self.use_proxy_checkbox.setChecked(False)
        self.use_proxy_checkbox.setStyleSheet(CHECKBOX_STYLE)
        hbox_use_proxy.addWidget(self.use_proxy_checkbox)

        self.notification_label = QLabel("", self)
        self.notification_label.setStyleSheet("""
            QLabel {
                background-color: rgba(50, 50, 50, 200);
                color: white;
                padding: 5px 10px;
                border-radius: 5px;
                font-weight: bold;
            }
        """)
        self.notification_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.notification_label.hide()
        hbox_use_proxy.addStretch()
        hbox_use_proxy.addWidget(self.notification_label)
        layout.addLayout(hbox_use_proxy)

        # Proxy and Port Inputs
        hbox_proxy_port = QHBoxLayout()
        proxy_label = QLabel("Прокси:")
        self.proxy_input = QLineEdit()
        self.proxy_input.setPlaceholderText("protocol://proxy")
        self.proxy_input.setStyleSheet("background-color: #323232; color: white;")
        self.proxy_input.returnPressed.connect(self.on_add_proxy)
        port_label = QLabel("Порт:")
        self.port_input = QLineEdit()
        self.port_input.setPlaceholderText("Port")
        self.port_input.setStyleSheet("background-color: #323232; color: white;")
        self.port_input.returnPressed.connect(self.on_add_proxy)
        hbox_proxy_port.addWidget(proxy_label)
        hbox_proxy_port.addWidget(self.proxy_input)
        hbox_proxy_port.addWidget(port_label)
        hbox_proxy_port.addWidget(self.port_input)
        layout.addLayout(hbox_proxy_port)

        # Authentication Inputs
        hbox_auth = QHBoxLayout()
        username_label = QLabel("Имя пользователя:")
        self.username_input = QLineEdit()
        self.username_input.setPlaceholderText("Username")
        self.username_input.setStyleSheet("background-color: #323232; color: white;")
        self.username_input.returnPressed.connect(self.on_add_proxy)
        password_label = QLabel("Пароль:")
        self.password_input = QLineEdit()
        self.password_input.setPlaceholderText("Password")
        self.password_input.setStyleSheet("background-color: #323232; color: white;")
        self.password_input.setEchoMode(QLineEdit.EchoMode.Password)
        self.password_input.returnPressed.connect(self.on_add_proxy)
        hbox_auth.addWidget(username_label)
        hbox_auth.addWidget(self.username_input)
        hbox_auth.addWidget(password_label)
        hbox_auth.addWidget(self.password_input)
        layout.addLayout(hbox_auth)

        # Proxy List and Buttons
        hbox3 = QHBoxLayout()

        self.saved_proxy_choice = ProxyListWidget(self)
        self.saved_proxy_choice.setSelectionMode(QListWidget.SelectionMode.ExtendedSelection)
        self.saved_proxy_choice.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
        self.saved_proxy_choice.deleteRequested.connect(self.on_delete_proxy)
        self.saved_proxy_choice.editRequested.connect(self.edit_selected_proxy)
        self.saved_proxy_choice.insertFromClipboardRequested.connect(self.insert_proxy_from_clipboard)
        hbox3.addWidget(self.saved_proxy_choice, 1)

        vbox_buttons = QVBoxLayout()

        self.add_proxy_button = QPushButton("Сохранить прокси")
        self.add_proxy_button.setStyleSheet(BUTTON_STYLE)
        self.add_proxy_button.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
        self.add_proxy_button.clicked.connect(self.on_add_proxy)

        self.delete_proxy_button = QPushButton("Удалить выбранные прокси")
        self.delete_proxy_button.setStyleSheet(BUTTON_STYLE_DELETE)
        self.delete_proxy_button.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
        self.delete_proxy_button.clicked.connect(self.on_delete_proxy)

        vbox_buttons.addStretch()
        vbox_buttons.addWidget(self.add_proxy_button)
        vbox_buttons.addSpacing(10)
        vbox_buttons.addWidget(self.delete_proxy_button)
        vbox_buttons.addStretch()

        hbox3.addLayout(vbox_buttons, 0)

        layout.addLayout(hbox3)

        # Service Selection
        hbox5 = QHBoxLayout()
        service_label = QLabel("Выбор сервиса:")
        self.service_group = QButtonGroup()
        self.ipinfo_radio = QRadioButton("ipinfo.io")
        self.ipapi_radio = QRadioButton("ipapi.co")
        self.ipapi_radio_new = QRadioButton("ip-api.com")
        self.service_group.addButton(self.ipinfo_radio)
        self.service_group.addButton(self.ipapi_radio)
        self.service_group.addButton(self.ipapi_radio_new)

        self.ipinfo_radio.setChecked(True)
        hbox5.addWidget(service_label)
        hbox5.addWidget(self.ipinfo_radio)
        hbox5.addWidget(self.ipapi_radio)
        hbox5.addWidget(self.ipapi_radio_new)
        layout.addLayout(hbox5)

        # Get Button
        self.get_button = QPushButton("Получить IP и ISP")
        self.get_button.setStyleSheet(BUTTON_STYLE)
        self.get_button.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
        self.get_button.clicked.connect(self.on_get_button)

        hbox_get = QHBoxLayout()
        hbox_get.addStretch()
        hbox_get.addWidget(self.get_button)
        hbox_get.addStretch()
        layout.addLayout(hbox_get)

        self.load_saved_proxies()
        self.load_settings()

        # System Tray
        self.tray_icon = QSystemTrayIcon(self)
        self.tray_icon.setIcon(self.icon)
        self.tray_icon.setToolTip('JustWoofIP')
        self.tray_icon.activated.connect(self.on_tray_icon_activated)
        self.create_tray_menu()
        self.tray_icon.show()

        # Подключаем сигнал изменения буфера обмена для отслеживания изменений
        clipboard = QApplication.clipboard()
        clipboard.dataChanged.connect(self.on_clipboard_change)

    def keyPressEvent(self, event):
        if event.modifiers() == Qt.KeyboardModifier.ControlModifier and event.key() == Qt.Key.Key_Z:
            self.undo_delete()
        elif event.modifiers() == (Qt.KeyboardModifier.ControlModifier | Qt.KeyboardModifier.ShiftModifier) and event.key() == Qt.Key.Key_Z:
            self.redo_delete()
        else:
            super().keyPressEvent(event)

    def create_menu(self):
        menubar = self.menuBar()

        # Файл
        file_menu = menubar.addMenu('Файл')

        load_action = QAction('Загрузить прокси из локального файла', self)
        load_action.triggered.connect(self.load_proxies_from_file)
        file_menu.addAction(load_action)

        load_url_action = QAction('Загрузить прокси из Интернета', self)
        load_url_action.triggered.connect(self.load_proxies_from_url_via_dialog)
        file_menu.addAction(load_url_action)

        exit_action = QAction('Выход', self)
        exit_action.triggered.connect(self.on_menu_exit)
        file_menu.addAction(exit_action)

        # Правка
        edit_menu = menubar.addMenu('Правка')
        
        # Undo/Redo для удаления
        self.undo_delete_action = QAction('Отменить удаление', self)
        self.undo_delete_action.triggered.connect(self.undo_delete)
        self.undo_delete_action.setEnabled(False)
        edit_menu.addAction(self.undo_delete_action)

        self.redo_delete_action = QAction('Повторить удаление', self)
        self.redo_delete_action.triggered.connect(self.redo_delete)
        self.redo_delete_action.setEnabled(False)
        edit_menu.addAction(self.redo_delete_action)

        # Настройки
        settings_menu = menubar.addMenu('Настройки')
        minimize_to_tray_action = QAction('Сворачивать в трей при закрытии', self, checkable=True)
        minimize_to_tray_action.setChecked(self.minimize_to_tray)
        minimize_to_tray_action.triggered.connect(self.on_minimize_to_tray_toggled)
        settings_menu.addAction(minimize_to_tray_action)

        # Справка
        help_menu = menubar.addMenu('Справка')
        about_action = QAction('О программе', self)
        about_action.triggered.connect(self.show_about_dialog)
        help_menu.addAction(about_action)

        menubar.setStyleSheet("""
            QMenuBar {
                background-color: #323232;
                color: white;
            }
            QMenuBar::item:selected {
                background-color: #464646;
            }
            QMenu {
                background-color: #323232;
                color: white;
            }
            QMenu::item:selected {
                background-color: #464646;
            }
            QMenu::item:pressed {
                background-color: #5a5a5a;
            }
            QMenu::item:disabled {
                color: #7f7f7f;
            }
        """)

    def create_tray_menu(self):
        menu = QMenu()
        show_action = QAction("Показать", self)
        exit_action = QAction("Выход", self)
        show_action.triggered.connect(self.on_tray_show)
        exit_action.triggered.connect(self.on_tray_exit)
        menu.addAction(show_action)
        menu.addAction(exit_action)
        self.tray_icon.setContextMenu(menu)

    def on_tray_icon_activated(self, reason):
        if reason == QSystemTrayIcon.ActivationReason.Trigger:
            self.on_tray_show()

    def on_tray_show(self):
        self.showNormal()
        self.activateWindow()

    def on_tray_exit(self):
        self.tray_icon.hide()
        self.close()

    def on_menu_exit(self):
        self.tray_icon.hide()
        self.close()

    def on_minimize_to_tray_toggled(self):
        self.minimize_to_tray = not self.minimize_to_tray

    def edit_selected_proxy(self):
        selected_items = self.saved_proxy_choice.selectedItems()
        if not selected_items:
            self.show_notification_inline("Не выбран прокси для редактирования", duration=2000)
            return

        item = selected_items[0]
        proxy_details = item.text()
        proxy_index = self.saved_proxy_choice.indexFromItem(item).row()
        new_details, ok = QInputDialog.getText(
            self, 
            "Редактировать прокси",
            "Введите новый прокси (protocol://proxy:port)", 
            QLineEdit.EchoMode.Normal, 
            proxy_details
        )

        if ok and new_details:
            parts = new_details.split("://")
            if len(parts) != 2 or ':' not in parts[1]:
                self.show_notification_inline("Некорректный формат прокси", duration=2000)
                return
            protocol, server_port = parts
            if protocol not in ['http', 'https', 'socks4', 'socks5']:
                self.show_notification_inline("Некорректный протокол", duration=2000)
                return
            if not all(part.isdigit() for part in server_port.split(':')[1:]):
                self.show_notification_inline("Порт должен быть числом", duration=2000)
                return

            username = None
            password = None

            if '@' in server_port:
                server_port, creds = server_port.split('@', 1)
                if ':' in creds:
                    username, password = creds.split(':')

            self.saved_proxies[proxy_index] = {'protocol': protocol, 'server_port': server_port}
            if username:
                self.saved_proxies[proxy_index]['username'] = self.fernet.encrypt(username.encode()).decode()
            if password:
                self.saved_proxies[proxy_index]['password'] = self.fernet.encrypt(password.encode()).decode()
            self.save_proxies()
            self.update_saved_proxy_list()
            self.show_notification_inline("Прокси обновлен", duration=2000)

    def insert_proxy_from_clipboard(self):
        clipboard = QApplication.clipboard()
        clipboard_text = clipboard.text().strip()

        if not clipboard_text:
            self.show_notification_inline("Буфер обмена пуст", 2000)
            return

        # Разбиваем текст буфера по строкам
        proxy_lines = clipboard_text.splitlines()
        proxies_to_add = []
        missing_protocol = False

        for proxy_text in proxy_lines:
            # Проверяем, соответствует ли текст формату прокси
            pattern = re.compile(
                r'^(?:(?P<protocol>[a-z]+):\/\/)?(?P<server>[^:@]+)(?::(?P<port>\d+))?(?:@(?P<username>[^:]+):(?P<password>.+))?$'
            )
            match = pattern.match(proxy_text.strip())

            if not match:
                self.show_notification_inline("Некорректный формат прокси в строке", 2000)
                continue

            proxy_data = match.groupdict()
            protocol = proxy_data.get('protocol')
            server = proxy_data.get('server')
            port = proxy_data.get('port')

            if not all([server, port]):
                self.show_notification_inline("Недостаточно данных для создания прокси", 2000)
                continue

            # Добавляем в список для дальнейшего добавления
            proxies_to_add.append(proxy_data)

            if protocol is None:
                missing_protocol = True

        # Если был найден хотя бы один прокси без протокола, запрашиваем у пользователя
        if missing_protocol:
            protocol, ok = QInputDialog.getItem(self, "Выбор протокола", "Выберите протокол:", ["http", "https", "socks4", "socks5"], 0, False)
            if not ok:  # Если пользователь отказался выбирать протокол, выходим
                return

        for proxy_data in proxies_to_add:
            protocol = proxy_data.get('protocol') or protocol  # Используем введенный пользователем протокол
            proxy_entry = {
                'protocol': protocol,
                'server_port': f"{proxy_data.get('server')}:{proxy_data.get('port')}"
            }

            username = proxy_data.get('username')
            password = proxy_data.get('password')

            if username:
                proxy_entry['username'] = self.fernet.encrypt(username.encode()).decode()
            if password:
                proxy_entry['password'] = self.fernet.encrypt(password.encode()).decode()

            # Проверка на дубликаты
            if any(p['protocol'] == proxy_entry['protocol'] and p['server_port'] == proxy_entry['server_port'] for p in self.saved_proxies):
                self.show_notification_inline("Прокси уже существует", 2000)
                continue

            self.saved_proxies.append(proxy_entry)

        self.save_proxies()
        self.update_saved_proxy_list()
        self.show_notification_inline("Прокси добавлены из буфера обмена", 2000)

    def closeEvent(self, event):
        self.save_settings()
        if self.minimize_to_tray:
            self.hide()
            event.ignore()
        else:
            self.tray_icon.hide()
            event.accept()

    def load_saved_proxies(self):
        self.saved_proxies = []
        if os.path.exists(PROXY_FILE):
            try:
                with open(PROXY_FILE, 'r', encoding='utf-8') as f:
                    encrypted_proxies = json.load(f)
                for proxy in encrypted_proxies:
                    decrypted_proxy = {
                        'protocol': proxy.get('protocol', 'socks5'),
                        'server_port': proxy.get('server_port', '')
                    }
                    if 'username' in proxy:
                        try:
                            decrypted_username = self.fernet.decrypt(proxy['username'].encode()).decode()
                            decrypted_proxy['username'] = decrypted_username
                        except Exception as e:
                            print(f"Ошибка расшифровки имени пользователя: {e}")
                    if 'password' in proxy:
                        try:
                            decrypted_password = self.fernet.decrypt(proxy['password'].encode()).decode()
                            decrypted_proxy['password'] = decrypted_password
                        except Exception as e:
                            print(f"Ошибка расшифровки пароля: {e}")
                    self.saved_proxies.append(decrypted_proxy)
                self.update_saved_proxy_list()
            except Exception as e:
                print(f"Ошибка загрузки прокси: {e}")
        self.update_proxy_checkbox_state()

    def save_proxies(self):
        proxies_to_save = []
        for proxy in self.saved_proxies:
            proxy_copy = {
                'protocol': proxy.get('protocol', 'socks5'),
                'server_port': proxy.get('server_port', '')
            }
            if 'username' in proxy and proxy['username']:
                proxy_copy['username'] = self.fernet.encrypt(proxy['username'].encode()).decode()
            if 'password' in proxy and proxy['password']:
                proxy_copy['password'] = self.fernet.encrypt(proxy['password'].encode()).decode()
            proxies_to_save.append(proxy_copy)

        os.makedirs(os.path.dirname(PROXY_FILE), exist_ok=True)
        try:
            with open(PROXY_FILE, 'w', encoding='utf-8') as f:
                json.dump(proxies_to_save, f, ensure_ascii=False, indent=4)
        except Exception as e:
            print(f"Ошибка сохранения прокси: {e}")

    def update_saved_proxy_list(self):
        self.saved_proxy_choice.clear()
        for proxy in self.saved_proxies:
            display_text = f"{proxy['protocol']}://{proxy['server_port']}"
            if proxy.get('username') and proxy.get('password'):
                display_text += f" (***:***)"
            elif proxy.get('username'):
                display_text += f" (***:---)"
            elif proxy.get('password'):
                display_text += f" (---:***)"
            self.saved_proxy_choice.addItem(display_text)
        self.update_proxy_checkbox_state()

    def update_proxy_checkbox_state(self):
        if len(self.saved_proxies) == 0:
            self.use_proxy_checkbox.setChecked(False)
            self.use_proxy_checkbox.setEnabled(False)
        else:
            self.use_proxy_checkbox.setEnabled(True)

    def load_settings(self):
        if os.path.exists(SETTINGS_FILE):
            try:
                with open(SETTINGS_FILE, 'r', encoding='utf-8') as f:
                    settings = json.load(f)
                use_proxy = settings.get('use_proxy', False)
                minimize_to_tray = settings.get('minimize_to_tray', False)
                selected_proxy_index = settings.get('selected_proxy_index', -1)
                selected_service = settings.get('selected_service', 'ipinfo.io')

                self.use_proxy_checkbox.setChecked(use_proxy)
                self.minimize_to_tray = minimize_to_tray
                if 0 <= selected_proxy_index < len(self.saved_proxies):
                    self.saved_proxy_choice.setCurrentRow(selected_proxy_index)
                if selected_service == 'ipinfo.io':
                    self.ipinfo_radio.setChecked(True)
                elif selected_service == 'ipapi.co':
                    self.ipapi_radio.setChecked(True)
                elif selected_service == 'ip-api.com':
                    self.ipapi_radio_new.setChecked(True)
            except Exception as e:
                print(f"Ошибка загрузки настроек: {e}")
        else:
            self.use_proxy_checkbox.setChecked(False)
            self.minimize_to_tray = False
            self.ipinfo_radio.setChecked(True)

    def save_settings(self):
        selected_service = 'ipinfo.io'
        if self.ipinfo_radio.isChecked():
            selected_service = 'ipinfo.io'
        elif self.ipapi_radio.isChecked():
            selected_service = 'ipapi.co'
        elif self.ipapi_radio_new.isChecked():
            selected_service = 'ip-api.com'

        settings = {
            'use_proxy': self.use_proxy_checkbox.isChecked(),
            'minimize_to_tray': self.minimize_to_tray,
            'selected_proxy_index': self.saved_proxy_choice.currentRow(),
            'selected_service': selected_service
        }
        os.makedirs(os.path.dirname(SETTINGS_FILE), exist_ok=True)
        try:
            with open(SETTINGS_FILE, 'w', encoding='utf-8') as f:
                json.dump(settings, f, ensure_ascii=False, indent=4)
        except Exception as e:
            print(f"Ошибка сохранения настроек: {e}")

    def on_get_button(self):
        use_proxy = self.use_proxy_checkbox.isChecked()
        if self.ipinfo_radio.isChecked():
            service_url = 'https://ipinfo.io/json'
        elif self.ipapi_radio.isChecked():
            service_url = 'https://ipapi.co/json/'
        elif self.ipapi_radio_new.isChecked():
            service_url = 'http://ip-api.com/json/'
        else:
            service_url = 'https://ipinfo.io/json'

        self.current_service_url = service_url

        proxy_data = None
        if use_proxy:
            selected_indices = self.saved_proxy_choice.selectedIndexes()
            if selected_indices:
                selected_index = selected_indices[0].row()
                if 0 <= selected_index < len(self.saved_proxies):
                    proxy_data = self.saved_proxies[selected_index].copy()
            elif len(self.saved_proxies) > 0:
                proxy_data = self.saved_proxies[0].copy()

        ip, isp, city, country = get_ip_and_isp(proxy_data, use_proxy, service_url)
        self.ip_text.setText(ip)
        self.isp_text.setText(isp)
        self.city_text.setText(city)
        self.country_text.setText(country)

        self.update_country_flag(country)

    def on_add_proxy(self):
        proxy = self.proxy_input.text().strip()
        port = self.port_input.text().strip()
        username = self.username_input.text().strip()
        password = self.password_input.text().strip()

        if not proxy or not port:
            self.show_notification_inline("Прокси и порт обязательны", duration=2000)
            return

        if password and not username:
            self.show_notification_inline("Имя пользователя обязательно при наличии пароля", duration=2000)
            return

        if '://' in proxy and re.match(r'^[a-zA-Z]+://', proxy):
            try:
                protocol, server = proxy.split('://', 1)
            except ValueError:
                self.show_notification_inline("Некорректный формат прокси", duration=2000)
                return
        else:
            protocol = 'socks5'
            server = proxy

        if not port.isdigit():
            self.show_notification_inline("Порт должен быть числом", duration=2000)
            return

        server_port = f"{server}:{port}"

        # Проверка на дубликаты
        if any(p['protocol'] == protocol and p['server_port'] == server_port for p in self.saved_proxies):
            self.show_notification_inline("Прокси с таким адресом и портом уже существует", duration=2000)
            return

        proxy_data = {'protocol': protocol, 'server_port': server_port}

        if username:
            proxy_data['username'] = self.fernet.encrypt(username.encode()).decode()
        if password:
            proxy_data['password'] = self.fernet.encrypt(password.encode()).decode()

        self.saved_proxies.append(proxy_data)
        self.save_proxies()
        self.update_saved_proxy_list()

        # Очищаем поля ввода
        self.proxy_input.clear()
        self.port_input.clear()
        self.username_input.clear()
        self.password_input.clear()

        self.show_notification_inline("Прокси сохранен", duration=2000)

    def on_delete_proxy(self):
        selected_indices = self.saved_proxy_choice.selectedIndexes()
        if not selected_indices:
            self.show_notification_inline("Не выбран прокси для удаления", duration=2000)
            return
        
        # Для хранения пар индекс-прокси
        deleted_proxies_with_indices = [(index.row(), self.saved_proxies[index.row()]) for index in selected_indices]
        
        # Удаление выбранных прокси из списка
        for row, _ in sorted(deleted_proxies_with_indices, key=lambda x: x[0], reverse=True):
            del self.saved_proxies[row]

        self.save_proxies()
        self.update_saved_proxy_list()

        total = self.saved_proxy_choice.count()
        if total > 0:
            self.saved_proxy_choice.setCurrentRow(0)

        # Добавляем удаленные прокси в стек Undo
        self.deleted_proxies_stack.append(deleted_proxies_with_indices)
        # Очистить стек Redo при новом удалении
        self.redo_deleted_proxies_stack.clear()

        self.show_notification_inline("Выбранные прокси удалены", duration=2000)
        self.update_undo_redo_actions()

    def undo_delete(self):
        if not self.deleted_proxies_stack:
            self.show_notification_inline("Нечего восстанавливать", duration=2000)
            return

        # Получаем последний удаленный прокси
        last_deleted_with_indices = self.deleted_proxies_stack.pop()
        # Добавляем их на исходные места
        for row, proxy in sorted(last_deleted_with_indices, key=lambda x: x[0]):
            self.saved_proxies.insert(row, proxy)

        self.save_proxies()
        self.update_saved_proxy_list()

        # Добавляем в стек Redo
        self.redo_deleted_proxies_stack.append(last_deleted_with_indices)

        self.show_notification_inline("Удаление отменено", duration=2000)
        self.update_undo_redo_actions()

    def redo_delete(self):
        if not self.redo_deleted_proxies_stack:
            self.show_notification_inline("Нечего удалять повторно", duration=2000)
            return

        # Получаем последний из стека Redo
        last_redo_with_indices = self.redo_deleted_proxies_stack.pop()
        # Находим и удаляем его снова
        for row, _ in sorted(last_redo_with_indices, key=lambda x: x[0], reverse=True):
            del self.saved_proxies[row]

        self.save_proxies()
        self.update_saved_proxy_list()
        # Добавляем обратно в стек Undo
        self.deleted_proxies_stack.append(last_redo_with_indices)
        self.show_notification_inline("Повторное удаление выполнено", duration=2000)
        self.update_undo_redo_actions()

    def undo_copy(self):
        if not self.copy_history_stack:
            self.show_notification_inline("Нечего отменять", duration=2000)
            return

        # Получаем последний скопированный текст
        last_copy = self.copy_history_stack.pop()
        # Сохраняем текущий буфер обмена в стек Redo
        current_clipboard = QApplication.clipboard().text()
        self.redo_copy_history_stack.append(current_clipboard)
        # Восстанавливаем прошлый текст
        QApplication.clipboard().setText(last_copy)
        self.show_notification_inline("Отмена копирования", duration=2000)
        self.update_undo_redo_copy_actions()

    def redo_copy(self):
        if not self.redo_copy_history_stack:
            self.show_notification_inline("Нечего повторять", duration=2000)
            return

        # Получаем текст для повтора копирования
        redo_copy = self.redo_copy_history_stack.pop()
        # Сохраняем текущий буфер обмена в стек Undo
        current_clipboard = QApplication.clipboard().text()
        self.copy_history_stack.append(current_clipboard)
        # Устанавливаем текст из Redo
        QApplication.clipboard().setText(redo_copy)
        self.show_notification_inline("Повторное копирование", duration=2000)
        self.update_undo_redo_copy_actions()

    def update_undo_redo_actions(self):
        # Управление доступностью Undo/Redo для удаления
        has_undo_delete = len(self.deleted_proxies_stack) > 0
        has_redo_delete = len(self.redo_deleted_proxies_stack) > 0

        self.undo_delete_action.setEnabled(has_undo_delete)
        self.redo_delete_action.setEnabled(has_redo_delete)

    def update_undo_redo_copy_actions(self):
        # Управление доступностью Undo/Redo для копирования
        has_undo_copy = len(self.copy_history_stack) > 0
        has_redo_copy = len(self.redo_copy_history_stack) > 0

        # Эти действия были определены, но вы можете их реализовать, если потребуется
        # self.undo_copy_action.setEnabled(has_undo_copy)
        # self.redo_copy_action.setEnabled(has_redo_copy)

    def copy_ip(self):
        ip = self.ip_text.text().strip()
        if ip and ip != 'N/A' and ip != 'Ошибка':
            clipboard = QApplication.clipboard()
            current_text = clipboard.text()
            # Добавляем текущий текст в стек Undo
            self.copy_history_stack.append(current_text)
            # Очищаем стек Redo при новом копировании
            self.redo_copy_history_stack.clear()
            # Копируем IP в буфер обмена
            clipboard.setText(ip)
            self.show_notification_inline("IP-адрес скопирован", duration=2000)
            self.update_undo_redo_copy_actions()
        else:
            self.show_notification_inline("Нечего копировать", duration=2000)

    def on_clipboard_change(self):
        # Можно добавить дополнительную логику при необходимости
        pass

    def show_notification_inline(self, message, duration=1000):
        self.notification_label.setText(message)
        self.notification_label.adjustSize()
        self.notification_label.setFixedHeight(30)
        self.notification_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.notification_label.show()
        QTimer.singleShot(duration, self.notification_label.hide)

    def show_about_dialog(self):
        dialog = AboutDialog(self)
        dialog.exec()

    def select_all_proxies(self):
        self.saved_proxy_choice.selectAll()

    def update_country_flag(self, country_name):
        if not country_name or country_name == 'N/A' or country_name == 'Ошибка':
            if os.path.exists(ERROR_ICON_PATH):
                error_pixmap = QPixmap(ERROR_ICON_PATH)
                self.flag_label.setPixmap(error_pixmap)
            else:
                self.flag_label.setPixmap(QPixmap())
            return

        try:
            country = pycountry.countries.lookup(country_name)
        except LookupError:
            try:
                search_results = pycountry.countries.search_fuzzy(country_name)
                country = search_results[0] if search_results else None
            except:
                country = None

        if country:
            country_code = country.alpha_2.lower()
            flag_url = f"https://flagcdn.com/32x24/{country_code}.png"

            try:
                response = requests.get(flag_url, timeout=10)
                if response.status_code == 200:
                    pixmap = QPixmap()
                    pixmap.loadFromData(response.content)
                    self.flag_label.setPixmap(pixmap)
                else:
                    self.set_error_icon()
            except Exception as e:
                self.set_error_icon()
        else:
            print(f"Не удалось найти код страны для: {country_name}")
            self.set_error_icon()

    def set_error_icon(self):
        if os.path.exists(ERROR_ICON_PATH):
            error_pixmap = QPixmap(ERROR_ICON_PATH)
            self.flag_label.setPixmap(error_pixmap)
        else:
            self.flag_label.setPixmap(QPixmap())

    def open_service_url(self):
        if self.current_service_url:
            # Открываем правильный URL в зависимости от текущего сервиса
            if 'ip-api.com' in self.current_service_url:
                QDesktopServices.openUrl(QUrl("https://ip-api.com/"))
            else:
                QDesktopServices.openUrl(QUrl(self.current_service_url))
        else:
            self.show_notification_inline("Сервис не выбран", duration=2000)

    def load_proxies_from_file(self):
        file_dialog = QFileDialog(self)
        file_dialog.setNameFilter("Text Files (*.txt);;All Files (*)")
        if file_dialog.exec():
            file_path = file_dialog.selectedFiles()[0]
            file_name = os.path.basename(file_path).lower()

            # Определяем протокол по имени файла
            default_protocol = self.determine_protocol_by_inspection(file_name)
            self.load_proxies_from_path(file_path, default_protocol)

    def load_proxies_from_url_via_dialog(self):
        url, ok = QInputDialog.getText(
            self, 
            "Загрузить прокси из Интернета",
            "Введите URL-адрес файла с прокси:"
        )
        if ok and url:
            self.load_proxies_from_url(url)

    def determine_protocol_by_inspection(self, name):
        # Определяем протокол по имени файла
        protocol_position = max(name.lower().rfind("https"), name.lower().rfind("http"))
        if protocol_position != -1:
            part = name.lower()[protocol_position:]
            if part:
                if 'socks4' in part:
                    return 'socks4'
                elif 'socks5' in part:
                    return 'socks5'
                elif part.startswith('https'):
                    return 'https'
                elif part.startswith('http'):
                    return 'http'
        return 'http'  # По умолчанию

    def load_proxies_from_path(self, path, default_protocol=None):
        try:
            with open(path, 'r', encoding='utf-8') as file:
                new_proxies = file.readlines()

            added_count = self.process_proxies(new_proxies, default_protocol)
            if added_count > 0:
                self.save_proxies()
                self.update_saved_proxy_list()
                self.show_notification_inline(f"Добавлено {added_count} новых прокси(ей)", duration=2000)
            else:
                self.show_notification_inline("Новых прокси не найдено", duration=2000)

        except Exception as e:
            print(f"Ошибка загрузки прокси из файла: {e}")
            self.show_notification_inline("Ошибка загрузки из файла", duration=2000)

    def load_proxies_from_url(self, url):
        url = url.strip()
        if not url:
            self.show_notification_inline("URL обязателен", duration=2000)
            return

        if "github.com" in url and "blob" in url:
            url = url.replace("github.com", "raw.githubusercontent.com").replace("/blob/", "/")

        try:
            response = requests.get(url, timeout=10)
            response.raise_for_status()
            new_proxies = response.text.splitlines()

            default_protocol = self.determine_protocol_by_inspection(url)
            added_count = self.process_proxies(new_proxies, default_protocol)

            if added_count > 0:
                self.save_proxies()
                self.update_saved_proxy_list()
                self.show_notification_inline(f"Добавлено {added_count} новых прокси(ей)", duration=2000)
            else:
                self.show_notification_inline("Новых прокси не найдено", duration=2000)

        except requests.RequestException as e:
            print(f"Ошибка загрузки прокси из сети: {e}")
            self.show_notification_inline("Ошибка загрузки из URL", duration=2000)

    def process_proxies(self, proxy_lines, default_protocol=None):
        added_count = 0
        for line in proxy_lines:
            proxy_line = line.strip()
            if not proxy_line:
                continue

            protocol, server_port = None, None
            username, password = None, None

            if '://' in proxy_line and re.match(r'^[a-zA-Z]+://', proxy_line):
                parts = proxy_line.split('://', 1)
                if len(parts) == 2:
                    protocol, remainder = parts
                else:
                    protocol, remainder = default_protocol, proxy_line
            else:
                protocol = default_protocol
                remainder = proxy_line

            if '@' in remainder:
                server_port, creds = remainder.split('@', 1)
                if ':' in creds:
                    username, password = creds.split(':', 1)
            elif ':' in remainder:
                parts = remainder.split(':')
                if len(parts) == 4:
                    server_port = f"{parts[0]}:{parts[1]}"
                    username = parts[2]
                    password = parts[3]
                elif len(parts) == 2:
                    server_port = remainder

            if not server_port or not protocol or ':' not in server_port:
                continue

            server, port = server_port.split(':', 1)

            if not port.isdigit():
                continue

            proxy_data = {'protocol': protocol, 'server_port': f"{server}:{port}"}

            if username:
                proxy_data['username'] = self.fernet.encrypt(username.encode()).decode()
            if password:
                proxy_data['password'] = self.fernet.encrypt(password.encode()).decode()

            # Проверка на дубликаты
            if not any(p['protocol'] == proxy_data['protocol'] and p['server_port'] == proxy_data['server_port'] for p in self.saved_proxies):
                self.saved_proxies.append(proxy_data)
                added_count += 1

        return added_count
        

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = MyApp()
    ex.show()
    sys.exit(app.exec())