"""
Класс страницы добавления файлов.
"""
import datetime
import os
import re
import sys
import traceback
from pathlib import Path
from typing import List

import axipy
from PySide2.QtCore import Slot, Qt, Signal, QObject
from PySide2.QtGui import QDropEvent, QDragEnterEvent, QDragMoveEvent
from PySide2.QtWidgets import QWizardPage, QVBoxLayout, QLabel, QListWidget, QPushButton, QLineEdit, QComboBox, \
    QProgressBar, QWidget, QWizard, QMessageBox, QFileDialog, QDialog, QDialogButtonBox, QListView, \
    QAbstractItemView, QTreeView, QListWidgetItem
from axipy import CoordSystem, tr, AxipyAnyCallableTask, task_manager, data_manager, provider_manager, CurrentSettings
from osgeo import gdal

from .. import helper
from .. import wizardconverter
from ..helper import debug

# Список драйверов для загрузки из json в combobox на странице добавления входных файлов
main_cbox_drivers = list(("MapInfo File", "DGN", "DXF", "GML", "KML", "ESRI Shapefile", "SQLite"))


class QListWidgetCustom(QListWidget):

    def __init__(self, *args, parent: 'WelcomePage', **kwargs) -> None:
        super().__init__(*args, **kwargs)
        self._parent = parent
        self.setParent(parent)
        self.setAcceptDrops(True)

    def dragEnterEvent(self, e: QDragEnterEvent) -> None:
        if e.mimeData().hasUrls():
            e.accept()
        else:
            e.ignore()

    def dragMoveEvent(self, e: QDragMoveEvent):
        if e.mimeData().hasUrls():
            e.accept()
        else:
            e.ignore()

    def dropEvent(self, e: QDropEvent) -> None:
        if e.mimeData().hasUrls():
            e.accept()
            urls = e.mimeData().urls()
            fnames = [str(Path(url.toLocalFile())) for url in urls]
            self._parent.process_fnames(fnames)
        else:
            e.ignore()


class WelcomePage(QWizardPage):
    """
    Начальная страница, служит для добавления файлов, авто. определения входного формата и входной СК, выбора выходного
    формата и выбора выходной папки.
    """

    def __init__(self, wizard) -> None:
        super().__init__()

        self.wzrd = wizard  # type: wizardconverter.WizardConverter

        # Вертикальная разметка
        input_files_vbox = QVBoxLayout()

        # Метка для списка входных файлов
        label = QLabel(tr("Список входных файлов"))
        input_files_vbox.addWidget(label)
        # Список входных файлов
        self.list_widget = QListWidgetCustom(parent=self)
        input_files_vbox.addWidget(self.list_widget)
        self.selected_filter = ""  # Последний выбранный фильтр диалога открытия файлов
        # Кнопки взаимодействия со списком входных файлов
        self.btn_open_file = QPushButton(tr("Добавить файл(ы)"),
                                         clicked=lambda txt="btn_open_file": self.open_files(txt))
        self.btn_open_folder = QPushButton(tr("Добавить папки"),
                                           clicked=lambda txt="btn_open_folder": self.open_files(txt))
        self.btn_delete = QPushButton(tr("Удалить"), clicked=lambda txt="btn_delete": self.btn_clicked(txt))
        self.btn_clear = QPushButton(tr("Очистить"), clicked=lambda txt="btn_clear": self.btn_clicked(txt))
        input_files_vbox.addLayout(
            helper.create_hbox(self.btn_open_file, self.btn_open_folder, self.btn_delete, self.btn_clear))

        # Размер для метки и поля
        labels_h = 24

        # Метка и поле для отображения входного формата
        input_format_label = QLabel(tr("Входной формат"))
        input_format_label.setMinimumHeight(labels_h)
        self.line_edit_inp_format = QLineEdit()
        self.line_edit_inp_format.setReadOnly(True)
        self.line_edit_inp_format.setTextMargins(2, 0, 0, 0)
        self.line_edit_inp_format.setMaximumWidth(300)
        self.line_edit_inp_format.setToolTip(tr('Формат входных файлов'))
        input_files_vbox.addLayout(helper.create_hbox(input_format_label, self.line_edit_inp_format))

        # Метка и поле для выбора выходного формата
        out_format_label = QLabel(tr("Выходной формат"))
        out_format_label.setMinimumHeight(labels_h)
        self.cbox_out_format = QComboBox(objectName="out_cbox_format")
        # Добавление имен драйверов в основной ComboBox
        cbox_items = [self.wzrd.drivers[driver]["name"] for driver in main_cbox_drivers]
        self.cbox_out_format.addItems(cbox_items)
        # Реализация добавления дополнительных форматов в основной cbox
        str_ = tr("Выбрать другой формат ...")
        self.cbox_out_format.addItem(str_)
        self.cbox_out_format.activated.connect(self.open_dialog_choose_out_format)
        self.cbox_out_format.setMinimumWidth(self.line_edit_inp_format.width())
        self.cbox_out_format.setToolTip(tr("Формат, в который будут преобразованы входные файлы"))
        self.last_cbox_index = 0  # Для открытия предыдущего значения combobox
        input_files_vbox.addLayout(helper.create_hbox(out_format_label, self.cbox_out_format))

        # Выходной путь
        # Метка выбора выходной директории
        label = QLabel(tr("Выходная папка"))
        label.setMinimumHeight(labels_h)
        input_files_vbox.addWidget(label)
        # Поле и кнопка выбора выходной директории
        self.out_dir_path_line_edit = QLineEdit()
        self.out_dir_path_line_edit.setReadOnly(True)
        self.out_dir_path_line_edit.setToolTip(
            tr("Папка, в которой будут создаваться папки с сконвертированными файлами"))
        self.btn_open_save_path = QPushButton("...", clicked=lambda txt="btn_open_dir_path": self.btn_clicked(txt))
        self.btn_open_save_path.setMaximumWidth(20)
        hbox = helper.create_hbox(self.out_dir_path_line_edit, self.btn_open_save_path)

        input_files_vbox.addLayout(hbox)
        # Имя набора
        label = QLabel(tr("Папка результатов"))
        self.results_folder_name_line_edit = QLineEdit()
        self.results_folder_name_line_edit.setMinimumWidth(self.line_edit_inp_format.width())
        self.results_folder_name_line_edit.setToolTip(
            tr('Папка с этим именем будет создана внутри “Выходной папки” и ') +
            tr('в неё будут помещены сконвертированные файлы'))
        hbox = helper.create_hbox(label, self.results_folder_name_line_edit)
        spacing = 80
        hbox.insertSpacing(0, spacing)
        input_files_vbox.addLayout(hbox)

        # # Метка и галочка для пропуска исключений
        # label = QLabel(tr("Продолжить после ошибки"))
        # label.setWordWrap(True)
        # label.setFixedWidth(self.wzrd.wizard_width * 25 // 100)
        # self.check_box_skipfailures = QCheckBox()
        # self.check_box_skipfailures.setToolTip(
        #     tr("Продолжить конвертацию слоя, если не удалось сконвертировать запись в таблице"))
        # self.check_box_skipfailures.stateChanged.connect(lambda: setattr(
        #     self.wzrd, "skip_failures", self.check_box_skipfailures.isChecked()))
        # hbox = helper.create_hbox(label, self.check_box_skipfailures)
        # hbox.addStretch()
        # input_files_vbox.addLayout(hbox)

        # Progress bar для открытия входных файлов в потоке
        self.pbar = QProgressBar()
        self.pbar.hide()
        input_files_vbox.addWidget(self.pbar)

        # Добавление разметки через виджет для реализации временной блокировки гуи
        self.input_widget = QWidget()
        self.input_widget.setLayout(input_files_vbox)
        vbox = QVBoxLayout()
        vbox.addWidget(self.input_widget)
        self.setLayout(vbox)

        def get_all_ext_str() -> str:
            text_arg = tr("Все поддерживаемые форматы")
            all_ext_list = []

            def extend_list(str_arg: str) -> None:
                result_arg = driver.get(str_arg, None)
                if result_arg:
                    result_arg = helper.ensure_list(result_arg)
                    all_ext_list.extend(result_arg)

            for driver in self.wzrd.drivers.values():
                extend_list("inp_ext")
                extend_list("out_ext")

            ext = ";*".join(all_ext_list)
            ext = f"{text_arg} (*{ext})"

            return ext

        all_ext_str = get_all_ext_str()

        # Список добавленных в json масок расширений для подсказки пользователю
        self.mask_list = ["{} (*.*)".format(tr("Все файлы"))]
        for driver in main_cbox_drivers:
            file_dialog_masks = self.wzrd.drivers[driver].get("file_dialog_masks", None)
            if file_dialog_masks:
                file_dialog_masks = helper.ensure_list(file_dialog_masks)
                self.mask_list.extend(file_dialog_masks)
        self.mask_list.insert(0, all_ext_str)

        # Обработка linux расширений (верхний/нижний регистр)
        if sys.platform.startswith("linux"):
            p = re.compile(r"(\*\.[^*][^\s)]*)")
            linux_list = ["{} (*.*)".format(tr("Все файлы"))]
            for elem in self.mask_list:
                if p.findall(elem):
                    upper_string = " ".join([ext.upper() for ext in p.findall(elem)])
                    out_string = elem.replace(")", " " + upper_string + ")")
                    linux_list.append(out_string)
            self.mask_list = linux_list
        # Итоговая сборка строки фильтра
        self.mask_list = ";;".join(self.mask_list)

        # Задача, выполняемая в потоке
        self.task = None
        self.signals = None

    @property
    def auto_rect_needed(self) -> bool:
        return self.wzrd.auto_rect_needed

    @auto_rect_needed.setter
    def auto_rect_needed(self, value: bool) -> None:
        self.wzrd.auto_rect_needed = value

    def initializePage(self) -> None:
        """
        Первая инициализация страницы и обработка рестарта.
        """
        # Сброс внутренних значений
        self.wzrd.gdal_config_options = {}
        self.wzrd.input_path_list = list()
        self.wzrd.inp_gdal_format = None
        self.wzrd.out_ext = ""
        self.wzrd.inp_cs_initial = None
        self.wzrd.inp_cs = None
        self.wzrd.out_cs = None
        self.wzrd.reproject = False
        self.wzrd.open_options, self.wzrd.dataset, self.wzrd.layer = [list(), list(), list()]
        # Обработка закрытия с активным фоновым потоком
        self.wzrd.task_rejected = False

        # Инициализация гуи
        self.list_widget.clear()
        self.line_edit_inp_format.clear()

        # Инициализация выходной папки
        if self.wzrd.out_dir_path:
            txt = self.wzrd.out_dir_path
        else:
            documents_path = self.wzrd.documents_path
            if isinstance(documents_path, Path) and documents_path.is_dir():
                txt = str(documents_path)
            else:
                txt = ""
        self.out_dir_path_line_edit.setText(txt)

        self.results_folder_name_line_edit.setText(datetime.datetime.now().strftime("%Y_%m_%d_%H_%M_%S"))
        # self.check_box_skipfailures.setChecked(self.wzrd.skip_failures)
        self.input_widget.setEnabled(True)
        # Инициализация полоски прогресса
        self.pbar.hide()
        # Инициализация кнопок wizard
        layout = [QWizard.Stretch, QWizard.BackButton, QWizard.NextButton, QWizard.CancelButton]
        self.wzrd.setButtonLayout(layout)

        self.auto_rect_needed = False

    def isComplete(self) -> bool:
        """
        Включение кнопки далее если файлов в списке виджета больше 0.
        """

        if self.list_widget.count() > 0:
            return True
        else:
            return False

    def validatePage(self) -> bool:
        """
        Валидация страницы - корректность выбора выходной папки.
        """

        # Валидация выходной директории, введенной пользователем
        try:
            # Создается Path из текста в поле ввода
            dir_path = Path(self.out_dir_path_line_edit.text())
            if not (dir_path.is_absolute() and dir_path.is_dir()):
                raise Exception(tr("Для перехода далее, необходимо выбрать выходную папку."))

            results_dir_path = Path(self.results_folder_name_line_edit.text())
            path = dir_path / results_dir_path

            # Предупреждение о возможности перезаписи данных
            if os.path.exists(str(path)) and len(os.listdir(str(path))) != 0:

                m_box_critical = QMessageBox(
                    QMessageBox.Warning, tr("Выбор папки результатов"),
                    "{}\n{}\n{}\n \"{}\"\n{}\n{}".format(
                        tr("Внимание!"),
                        tr("Папка результатов не пустая."),
                        tr("Файлы в папке результатов"), str(path),
                        tr("могут быть перезаписаны."),
                        tr("Продолжить?")),
                    buttons=QMessageBox.Yes | QMessageBox.Cancel, parent=self)

                result = m_box_critical.exec_()
                if result == QMessageBox.Cancel:
                    return False

        except Exception as e:
            m_box = QMessageBox(QMessageBox.Warning, tr("Ошибка"),
                                "{}:\n{}".format(tr("Ошибка при выборе выходной папки"), e),
                                buttons=QMessageBox.Ok, parent=self)
            m_box.exec_()
            return False
        else:
            # Запомнить путь к выходной папке
            self.wzrd.out_dir_path = str(dir_path)
            self.wzrd.out_path = path

        # Чтение ввода с вход. и выход. cbox
        inp_gui_format = self.line_edit_inp_format.text()
        out_gui_format = self.cbox_out_format.currentText()

        # Получение имени gdal из названия драйвера в гуи
        inp_found = False
        out_found = False
        for k, v in self.wzrd.drivers.items():
            if inp_gui_format == v["name"]:
                self.wzrd.inp_gdal_format = k
                inp_found = True
            if out_gui_format == v["name"]:
                self.wzrd.out_gdal_format = k
                out_found = True
        if not inp_found or not out_found:
            if not inp_found:
                helper.notify(f"{inp_gui_format} not found in json.")
            if not out_found:
                helper.notify(f"{out_gui_format} not found in json.")
            return False

        # Если входная ск запомнилась
        if self.wzrd.inp_cs_initial:
            self.wzrd.inp_cs = self.wzrd.inp_cs_initial
        else:  # Иначе запомнить
            self.wzrd.inp_cs_initial = self.wzrd.inp_cs

        self.auto_rect_needed = False
        # check again if was changed
        if self.wzrd.out_gdal_format == "MapInfo File":
            # unpack cs
            cs_list: List[axipy.CoordSystem] = []
            for i in range(self.list_widget.count()):
                item = self.list_widget.item(i)
                data = item.data(Qt.UserRole)
                cs_list.append(data)

            if len(cs_list) > 1:
                first_cs = cs_list[0]
                for cs in cs_list[1:]:
                    # sets auto_rect_needed inside function
                    self.compare_cs_without_rect(first_cs, cs)

        return True

    # Thread

    def open_files(self, btn_type: str) -> None:
        """
        Открытие файлов при нажатии на кнопку добавить файл(ы). Запуск валидации добавленных файлов в отдельном
        потоке.
        """

        fnames = None
        if btn_type == "btn_open_folder":  # Добавление одной или нескольких папок в список файлов
            file_dialog = QFileDialog(parent=self, directory=str(self.wzrd.inp_dir_path))
            # Отображаются только папки
            file_dialog.setFileMode(QFileDialog.Directory)
            file_dialog.setOption(QFileDialog.ShowDirsOnly, True)
            # Отключение встроенного диалога позволяет выбрать несколько папок одновременно
            file_dialog.setOption(QFileDialog.DontUseNativeDialog, True)
            # Выбор нескольких папок выделением, shift, ctrl
            selection_mode = QAbstractItemView.ExtendedSelection

            list_view = file_dialog.findChild(QListView)
            if list_view:
                list_view.setSelectionMode(selection_mode)

            tree_view = file_dialog.findChild(QTreeView)
            if tree_view:
                tree_view.setSelectionMode(selection_mode)

            if file_dialog.exec_():
                fnames = file_dialog.selectedFiles()
        # Открывается диалог, получить один или несколько путей к файлу в виде списка
        elif btn_type == "btn_open_file":
            fnames, self.selected_filter = QFileDialog.getOpenFileNames(
                self, "", str(self.wzrd.inp_dir_path), self.mask_list, self.selected_filter)

        # Если файлы выбраны
        if fnames:
            self.process_fnames(fnames)

    def process_fnames(self, fnames: List[str]) -> None:
        if not fnames:
            return None
        self.input_widget.setEnabled(False)
        self.wzrd.starting_task()

        # Запомнить путь открытия файлов
        self.wzrd.inp_dir_path = Path(fnames[0]).parent
        CurrentSettings.LastOpenPath = self.wzrd.inp_dir_path
        fnames = [Path(elem) for elem in fnames]  # Преобразование str в Path

        gdal.UseExceptions()  # Включение режима исключений

        self.signals = Signals()
        self.signals.list_widget_append.connect(self.list_widget_append)
        # Создание задачи для потока
        self.task = AxipyAnyCallableTask(self.opn_and_get_cs, fnames)
        self.task.with_handler(False)
        self.task.progress_handler().finished.connect(self.finished)
        task_manager.start_task(self.task)

    def opn_and_get_cs(self, added_files: list) -> None:
        """
        Функция выполняется в отдельном потоке. Происходит проверка входных файлов, проверенные файлы добавляются
        во внутреннее значение.

        :param added_files: Файлы добавленные через диалог.
        """
        # Основной цикл по добавленным файлам
        for file in added_files:
            try:
                # Обработка польз. отмены
                if self.task.progress_handler().is_canceled():
                    return None
                # Обработка файлов, которые уже есть в списке
                if file in self.wzrd.input_path_list:
                    helper.notify("{} ({}).".format(
                        tr("Файл не был добавлен, так как уже есть в списке"), str(file)), 0)
                    continue
                # Обработка расширение неправильно распознаваемых gdal
                if file.suffix.lower() in (".mid", ".mws"):
                    helper.notify("{}({}).".format(
                        tr("Файл не был добавлен, расширение файла не поддерживается"), file), 0)
                    continue
                # gdal
                # Открытие, считывание данных, закрытие
                meta_data = None
                desc = None
                projection = None
                try:
                    ds = gdal.OpenEx(str(file))
                    if ds:  # dataset
                        driver = ds.GetDriver()
                        if driver:  # driver
                            meta_data = driver.GetMetadata()
                            desc = driver.GetDescription()
                        layer = ds.GetLayer()
                        if layer:  # layer
                            spat = layer.GetSpatialRef()
                            if spat:
                                if spat.IsLocal():
                                    str_ = spat.ExportToWkt()
                                    if str_:
                                        projection = f"wkt:{str_}"
                                else:
                                    str_ = spat.ExportToProj4()
                                    if str_:
                                        projection = f"proj:{str_}"
                except Exception as e:
                    raise Exception("Ошибка чтения gdal") from e
                finally:
                    ds = None

                # Проверка на содержание векторных данных
                if meta_data:
                    if "DCAP_VECTOR" not in meta_data or meta_data.get("DCAP_VECTOR", "") != "YES":
                        helper.notify(
                            "{}({}).".format(tr("Файл не добавлен, так как в нем нет векторных данных"), file), 0)
                        continue
                else:
                    helper.notify("{}({}).".format(tr("Файл не добавлен, не удалось прочитать метаданные"), file), 0)
                    continue

                # Задать название, если оно еще не задано
                if desc:
                    if not self.wzrd.inp_gdal_format:
                        self.wzrd.inp_gdal_format = desc
                        # Обработка файлов, чей формат gdal не совпадает с форматом уже добавленных файлов
                    if desc != self.wzrd.inp_gdal_format:
                        helper.notify("{}{}({}).".format(
                            tr("Файл не был добавлен, так как формат файла не совпадает "),
                            tr("с уже добавленными файлами"),
                            file), 0)
                        continue
                else:
                    helper.notify("{}({}).".format(tr("Файл не добавлен, не удалось прочитать формат gdal"), file), 0)
                    continue

                # Считывание проекции файла
                inp_cs = None
                # Открытие tab файлов для получения проекции Аксиомой
                if (
                        self.wzrd.inp_gdal_format == "MapInfo File"
                        and file.suffix.lower() == ".tab"
                ):
                    # Получение списка всех открытых tab файлов в Аксиоме
                    da_opened = [elem for elem in data_manager.all_objects if
                                 elem.provider == "TabDataProvider"]
                    # Поиск файла среди открытых
                    for table in da_opened:
                        path = table.properties.get("fileName", None)
                        if path and file == Path(path):
                            inp_cs = table.schema.coordsystem
                    # Если файл не найден, открываем hidden и закрываем
                    if inp_cs is None:
                        table = provider_manager.open_hidden({"src": str(file)})
                        if table:
                            inp_cs = table.schema.coordsystem
                            data_manager.remove(table)
                elif projection:
                    cs = CoordSystem.from_string(projection)
                    if projection.startswith("proj"):
                        # Пересоздание объекта CoordSystem из proj в prj, т.к. аксиома не всегда
                        # может корректно получить имя из proj
                        inp_cs = CoordSystem.from_prj(cs.prj)
                    else:
                        inp_cs = cs

                # Если система координат еще не инициализированна, и система координат файла прочитана
                if not self.wzrd.inp_cs and inp_cs:
                    self.wzrd.inp_cs = inp_cs  # Инициализировать систему координат прочитанной

                # Если входная ск и ск файла init
                if self.wzrd.inp_cs and inp_cs:
                    # Если они не равны
                    if not self.compare_cs_without_rect(self.wzrd.inp_cs, inp_cs):
                        helper.notify("{}{}({}).".format(
                            tr("Файл не был добавлен, так как система координат файла не совпадает "),
                            tr("с уже добавленными файлами"),
                            file), 0)
                        continue

                # Добавление проверенного входного файла в список файлов
                self.wzrd.input_path_list.append(file)
                if not isinstance(inp_cs, axipy.CoordSystem):
                    inp_cs = self.wzrd.default_cs
                self.signals.list_widget_append.emit(str(file), inp_cs)

            except Exception as e:
                if debug:
                    traceback.print_exc()
                helper.notify("{} ({}): {}".format(tr("Ошибка при открытии файла"), str(file.name), str(e)))

    def compare_cs_without_rect(self, cs1: axipy.CoordSystem, cs2: axipy.CoordSystem) -> bool:
        if cs1 == cs2:
            return True

        if cs1.rect == cs2.rect:
            return False

        self.wzrd.auto_rect_needed = True

        return cs1.wkt and cs1.wkt == cs2.wkt

    @Slot()
    def finished(self) -> None:
        """
        Выполняется после завершения задачи, выполняемой в потоке.
        """

        if self.task.progress_handler().is_canceled():
            helper.notify(tr("Открытие файлов прервано."), 0)

        # Обработка прочитанного формата gdal, которого нет в json
        self.line_edit_inp_format.setText(self.wzrd.inp_gdal_format)
        # Добавление прочитанного формата gdal на гуи и во внутреннее значение
        for k, v in self.wzrd.drivers.items():
            if k == self.wzrd.inp_gdal_format:
                self.line_edit_inp_format.setText(v["name"])
        # Добавление прочитанной СК во внутреннее значение
        if self.wzrd.inp_cs is None and len(self.wzrd.input_path_list) > 0:

            if (
                    self.wzrd.drivers[self.wzrd.inp_gdal_format].get("geo_ref", None) and
                    self.wzrd.drivers[self.wzrd.inp_gdal_format].get("coord_sys_support", True)
            ):
                self.wzrd.inp_cs = self.wzrd.default_cs
                helper.notify("{}. {}: {}".format(
                    tr("Не удалось прочитать входную координатную систему"),
                    tr("Установлено значение по умолчанию"),
                    self.wzrd.default_cs.name), 0)

        self.input_widget.setEnabled(True)

        layout = [QWizard.Stretch, QWizard.BackButton, QWizard.NextButton, QWizard.CancelButton]
        self.wzrd.setButtonLayout(layout)

        self.wzrd.finishing_task()
        # Отправить сигнал на проверку готовности страницы для вкл. "Далее"
        self.completeChanged.emit()

    @Slot(str, axipy.CoordSystem)
    def list_widget_append(self, path: str, cs: axipy.CoordSystem) -> None:
        """
        Добавление пути до файла в виджет со списком файлов, прокрутка списка вниз и вправо.
        """
        item = QListWidgetItem(path)
        item.setData(Qt.UserRole, cs)
        self.list_widget.addItem(item)
        self.list_widget.verticalScrollBar().setValue(self.list_widget.verticalScrollBar().maximum())
        self.list_widget.horizontalScrollBar().setValue(self.list_widget.horizontalScrollBar().maximum())

    # Gui Slots

    def btn_clicked(self, txt: str) -> None:
        """
        Изменение элементов ввода окна открытия файлов.

        :param: txt: str: Текстовый идентификатор кнопки
        """

        def clear() -> None:
            """
            Очистка всех входных элементов.
            """
            self.list_widget.clear()
            self.wzrd.input_path_list.clear()
            self.wzrd.inp_gdal_format = ""
            self.line_edit_inp_format.clear()
            self.wzrd.inp_cs_initial = None
            self.wzrd.inp_cs = None

        # Если нажата кнопка "Удалить"
        if txt == "btn_delete":
            i = self.list_widget.currentRow()
            if i != -1:
                self.list_widget.takeItem(i)
                self.wzrd.input_path_list.pop(i)
            if self.list_widget.count() == 0:
                clear()
            self.completeChanged.emit()

        # Если нажата кнопка "Очистить"
        elif txt == "btn_clear":
            clear()
            self.completeChanged.emit()

        # Если нажата кнопка "Выбрать директорию"
        elif txt == "btn_open_dir_path":

            lnd_text = self.out_dir_path_line_edit.text()
            # Проверка текста
            lnd_text = Path(lnd_text)
            if not lnd_text.is_dir():
                lnd_text = ""

            dir_path = QFileDialog.getExistingDirectory(self, dir=str(lnd_text))
            if dir_path:
                self.out_dir_path_line_edit.setText(dir_path)
                self.wzrd.out_dir_path = dir_path

    @Slot(int)
    def open_dialog_choose_out_format(self, i: int) -> None:
        """
        Показ диалога выбора дополнительного формата. Показывается если выбран последний элемент основного cbox.

        :param i: Индекс выбранного элемента в основном cbox.
        """

        class FormatDialog(QDialog):
            """
            Диалог выбора доп. формата.
            """

            def __init__(self, wzrd_page) -> None:
                super().__init__(wzrd_page)
                wzrd = wzrd_page.wzrd

                self.setFont(wzrd.font())
                self.setWindowTitle(tr("Выбор формата"))
                # Вертикальная разметка
                vbox = QVBoxLayout()
                self.setLayout(vbox)
                # Виджет - список дополнительных форматов
                self.list_widget = QListWidget(self)
                self.list_widget.setMinimumWidth(300)
                self.list_widget.setMinimumHeight(400)
                # Список заполняется именами драйверов не из основного списка и которые поддерживают создание
                if debug:
                    extra_drivers = [f'{k}: {v["name"]}' for k, v in wzrd.drivers.items()
                                     if k not in main_cbox_drivers and v["creation"] is True]
                else:
                    extra_drivers = [f'{v["name"]}' for k, v in wzrd.drivers.items()
                                     if k not in main_cbox_drivers and v["creation"] is True]
                self.list_widget.addItems(extra_drivers)
                vbox.addWidget(self.list_widget)
                # Кнопки ок и отмена диалога
                btn_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
                btn_box.accepted.connect(self.accept)
                btn_box.rejected.connect(self.reject)
                vbox.addWidget(btn_box)
                # Выбранный дополнительный драйвер
                self.custom_driver = None

                self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint)  # Выключение значка "?

            def accept(self) -> None:
                """
                При нажатии ок, добавляет выбранный драйвер из доп. списка в основной combobox.
                """
                # Запоминается выбранный драйвер из списка
                txt = self.list_widget.currentItem().text()
                if debug:
                    # Удаление ключа в начале названия. (Ключ был добавлен для удобства.)
                    txt = re.compile(r"[^:]*:\s").sub("", txt, 1)
                self.custom_driver = txt
                super().accept()

        cbox = self.cbox_out_format
        n = cbox.count()
        # Если сигнал пришел от выбора последнего элемента cbox
        if i == n - 1:
            # Вызов диалога выбора дополнительного выходного формата
            dlg = FormatDialog(self)
            if dlg.exec_() == QDialog.Accepted:
                # Главные драйвера + кнопка выбрать новый формат + добавленный драйвер
                if n == len(main_cbox_drivers) + 1 + 1:
                    # Убрать предыдущий
                    cbox.removeItem(n - 2)
                # Добавить выбранный
                cbox.insertItem(cbox.count() - 1, dlg.custom_driver)

                self.last_cbox_index = cbox.count() - 2
            # Фокус на выбранный
            cbox.setCurrentIndex(self.last_cbox_index)
        else:
            self.last_cbox_index = i


class Signals(QObject):
    """
    Сигналы для взаимодействия с гуи из потока
    """
    list_widget_append = Signal(str, axipy.CoordSystem)
