# VersionPickerDialog

## Обзор компонента

`Minecraft_launcher` — десктопный лаунчер Minecraft на Qt Quick. Каталог версий Mojang — это около
тысячи записей: релизы, снапшоты, старые беты и альфы. Раньше выбор версии был обычным выпадающим
списком на всю эту тысячу, и найти в нём, скажем, бету 1.7 можно было только поиском по точному
номеру.

`VersionPickerDialog` заменил тот список отдельным окном: слева категории, справа сами версии с
поиском сверху. Категории делят каталог на обозримые части, а поиск работает внутри выбранной.
Помимо выбора окно умеет удалять уже скачанные версии — с предупреждением о последствиях, потому
что версия весит десятки мегабайт, а поверх неё могут стоять профили модлоадеров.

Окно открывается из карточки сборки, когда пользователь выбирает, на какой версии Minecraft
собирается играть.

## Место в проекте и зависимости

В начале файла объявлена `pragma ComponentBehavior: Bound`. Импортирует `QtQuick`,
`QtQuick.Controls 2.15` и `QtQuick.Layouts 2.15`.

Работает с C++-типом `LauncherBackend` (`launcherbackend.h`, `QML_ELEMENT` в модуле
`Minecraft_launcher`), передаваемым снаружи в свойство `backend`. Читает у него свойства
`versionCatalog` и `catalogLoading`, вызывает `refreshVersionCatalog()`, `versionRemovalInfo()` и
`removeVersion()`.

Объявлен в `QML_FILES` модуля (`CMakeLists.txt`); использует ресурс `images/Trash.svg` из
`RESOURCES` того же модуля. Инстанцируется в [BuildsDialog](BuildsDialog.md).

Каталог приходит из C++ уже отсортированным (новые сверху), поэтому окно только отбирает записи и
порядок не трогает. Каждая запись каталога — объект с полями `id` (идентификатор версии), `label`
(подпись строки), `category` (ключ категории), `installed` (скачана ли) и `search`
(предвычисленная строка для поиска в нижнем регистре).

## Иерархия и роль

Корневой тип — `Dialog`: модальный, 720×480 px, нулевой внутренний отступ, закрывается по Escape и
щелчку мимо. Оформление задано вручную — тёмный фон со скруглением и акцентной рамкой.

Содержимое — `RowLayout` из двух частей, разделённых вертикальной линией: колонка категорий
шириной 150 px (`Repeater` по `categories`) и область версий. В области версий сверху поле поиска,
внизу флажок «Только установленные», между ними `ListView` со строками версий. Строка показывает
подпись, галочку для установленной версии и корзину удаления; галочка рядом со строкой —
единственное место, где видно, что именно скачано.

Отдельно объявлен вложенный `Dialog` подтверждения удаления шириной 420 px с красной рамкой. Он
привязан к тому же родителю, что и само окно, и центрируется в нём вручную, чтобы не оказаться
внутри области выбора.

Пустое состояние списка объясняется текстом по центру, который различает три случая: каталог ещё
грузится, каталог пуст (нет соединения) и по фильтру ничего не найдено.

## Свойства

| Свойство | Тип | По умолчанию | Обязательное | Описание |
|----------|-----|--------------|--------------|----------|
| `backend` | `var` | — | **Да** | Экземпляр `LauncherBackend` — источник каталога версий и исполнитель удаления. |
| `selectedId` | `string` | `""` | Нет | Идентификатор выбранной версии. Окно открывается с текущей версией, и по «Отмене» выбор возвращается к ней, потому что результат уходит наружу только через сигнал. Пустая строка — версия не выбрана, кнопка подтверждения выключена. |
| `category` | `string` | `"release"` | Нет | Ключ активной категории. Допустимые значения: `release` (релизы), `snapshot` (снапшоты), `old_beta` (беты), `old_alpha` (альфы), `other` (прочие — сюда попадают в том числе установленные профили модлоадеров). |
| `filterText` | `string` | `""` | Нет | Текст поиска. Сравнивается в нижнем регистре без учёта регистра с полем `search` записи каталога; поиск идёт внутри активной категории. |
| `installedOnly` | `bool` | `false` | Нет | Показывать только скачанные версии. |
| `categories` | `var` (только чтение) | список из пяти записей | Нет | Описание колонки категорий: массив объектов с полями `key` и переведённым `title`. Задаёт и порядок пунктов, и набор допустимых значений `category`. |
| `visibleEntries` | `var` (только чтение) | вычисляется | Нет | Отобранные строки каталога: записи активной категории, прошедшие флажок «только установленные» и текст поиска. Порядок наследуется от каталога. Служит моделью списка. |

## Сигналы

#### versionChosen(string versionId)

Версия подтверждена — кнопкой «Выбрать», двойным щелчком по строке или клавишей Enter в поле
поиска. В параметре приходит идентификатор версии.

Имя не `accepted()` сознательно: такой сигнал у `Dialog` уже есть и переопределить его нельзя.

Обработчик — карточка сборки — записывает выбранную версию в сборку. К моменту вызова обработчика
окно уже закрыто. При отмене сигнал не испускается, поэтому снаружи ничего откатывать не нужно.

## Методы

#### openFor(string versionId) : void

Открывает окно на переданной версии. Запоминает её в `selectedId`, очищает поиск, переключается на
категорию именно этой версии (а не всегда на релизы), просит бэкенд обновить каталог и
прокручивает список к выбранной строке.

#### categoryOf(string versionId) : string

Возвращает ключ категории версии по каталогу; для неизвестной версии — `release`.

#### indexOfSelected() : int

Позиция выбранной версии в `visibleEntries` или `-1`, если под текущим фильтром её не видно.

#### revealSelected() : void

Выставляет текущий индекс списка на выбранную версию и прокручивает список так, чтобы строка
оказалась по центру. Вызывается после каждой смены фильтра, категории или удаления.

#### acceptSelection() : void

Подтверждает выбор: испускает `versionChosen()` и закрывает окно. При пустом `selectedId` не
делает ничего.

#### catalogHas(string versionId) : bool

Есть ли версия в каталоге. Нужен после удаления: профиль модлоадера присутствовал в каталоге
только потому, что был установлен, и после удаления строка исчезает совсем.

#### askRemove(string versionId) : void

Спрашивает подтверждение перед удалением. Запрашивает у бэкенда `versionRemovalInfo()` и, если
версия действительно установлена, наполняет окно подтверждения: занимаемый объём, список
зависящих профилей модлоадеров и список сборок, которые эту версию используют. Идентификатор
запоминается в самом окне подтверждения, потому что к моменту ответа строка под курсором может
быть уже другой.

Окно подтверждения объясняет три вещи: файлы удалятся из `versions/` и освободится столько-то
мегабайт; профили модлоадеров поверх этой версии без неё не запустятся; библиотеки и ресурсы в
`libraries/` и `assets/` общие для всех версий и остаются на месте.

#### performRemove(string versionId) : void

Удаляет версию через бэкенд. Если удалена была именно выбранная версия и её больше нет в каталоге,
снимает выбор. В конце обновляет позицию списка.

## Взаимодействие с другими компонентами

**Со стороны родителя.** [BuildsDialog](BuildsDialog.md) задаёт `backend`, открывает окно вызовом
`openFor()` с текущей версией сборки и подписывается на `versionChosen()`, чтобы записать выбор.
Никаких других точек входа у окна нет — прямая запись `selectedId` снаружи не предполагается.

**Со стороны бэкенда.** `versionCatalog` и `catalogLoading` — привязки, от которых зависят и
модель списка, и текст пустого состояния: пришедшее обновление каталога пересчитывает
`visibleEntries` само. `refreshVersionCatalog()` вызывается при открытии окна, `removeVersion()` —
после подтверждения удаления.

**Клавиатура.** Фокус при открытии уходит в поле поиска. Стрелки вверх и вниз двигают выбор по
списку функцией `step()`, Enter подтверждает выбор, Escape закрывает окно.

## Пример использования

```qml
VersionPickerDialog {
    id: versionPicker
    parent: Overlay.overlay
    backend: launcherBackend
    onVersionChosen: (versionId) => buildCard.gameVersion = versionId
}

Button {
    text: buildCard.gameVersion || qsTr("Выбрать версию")
    onClicked: versionPicker.openFor(buildCard.gameVersion)
}
```

---

При создании этого документа использовался ИИ.
