# Буфер обмена

В разделе кратко описан буфер обмена как системный механизм операционных систем, а также особенности вставки данных из буфера обмена в системе Global ERP.  

## Общее описание буфера обмена

Буфер обмена — это системный механизм передачи данных между приложениями. Он используется при копировании и вставке и позволяет передавать не только текст, но и изображения, файлы и структурированные данные.

### Где хранится буфер обмена

- В Windows буфер обмена представляет собой централизованное хранилище в оперативной памяти системы. Данные копируются в буфер и сохраняются независимо от приложения-источника. При вставке (например, по `Ctrl+V`) активное приложение запрашивает данные из буфера в нужном формате.
- В Linux (`X11` и `Wayland`) буфер обмена работает как механизм передачи данных между приложениями. Фактически данные хранит приложение-владелец, а не сама система. При вставке активное приложение запрашивает данные у владельца буфера, и тот передает их по запросу. Если приложение-владелец закрывается, данные из буфера могут быть потеряны.

### В каком виде хранятся данные

В Windows данные хранятся как набор форматов `CF_*`. Например:

- `CF_UNICODETEXT` — текст;
- `CF_BITMAP` — изображение;
- `CF_HDROP` — список файлов.

Одни и те же данные могут одновременно присутствовать в нескольких форматах.

В Linux для представления данных используются MIME-типы. Наиболее распространенные варианты:

- `text/plain`;
- `text/html`;
- `image/png`.

### Как приложение получает данные

Данные из буфера обмена не передаются автоматически. Приложение получает их в момент вставки, например при нажатии `Ctrl+V`.

Процесс выглядит следующим образом:

1. Пользователь инициирует вставку.
2. Активное приложение запрашивает данные из буфера обмена в одном из доступных форматов.
3. Операционная система (или приложение-владелец буфера в Linux) возвращает данные.
4. Приложение получает данные в виде:
   - строки (например, текст или TSV);
   - бинарных данных (например, изображение);
   - списка файлов (если были скопированы файлы).

В системе Global ERP полученные данные далее передаются на сервер и приводятся к файлу `event.file`, с которым работает прикладная логика.


## Вставка данных из буфера обмена в системе

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

Пользователь копирует данные стандартным способом, например через `Ctrl+C`. При вставке (`Ctrl+V` или команда «Вставить») активное приложение инициирует получение данных из буфера обмена.

На стороне пользователя в этот момент происходит событие вставки, в рамках которого:

1. Приложение запрашивает данные из буфера обмена в одном из поддерживаемых форматов.
2. Полученные данные (или файлы) передаются на сервер.
3. На сервере они преобразуются во временный файл (`event.file`).
4. После этого вызывается метод `onClipboardPaste`, в который передается объект события `PasteEvent`.

Таким образом, извлечение данных из буфера обмена происходит на стороне пользователя в момент вставки, после чего данные передаются на сервер и преобразуются в файл для дальнейшей обработки.

Дальнейшая обработка выполняется не напрямую с содержимым буфера обмена, а с файлом, который был сформирован на сервере на основе переданных данных или файлов из буфера.

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

Если пользователь копирует диапазон ячеек, например из Excel, данные передаются как табличный набор строк и колонок. При дальнейшей обработке система разбирает их построчно и поколоночно, используя символы перевода строки и табуляции как разделители.

Если среди ячеек, в которые выполняется вставка, есть нередактируемые, сервер прерывает операцию и выводит сообщение об ошибке: **«Невозможно вставить значения: среди выделенных ячеек есть нередактируемые»**.

### Поддерживаемые форматы вставки

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

Поддерживаются следующие типы:

- `text/plain (Plain Text)` — обычный текст без форматирования;
- `application/xml` и `text/xml` — XML-документы;
- `text/html` — HTML-документы.

### Настройка выборки

Поддержка вставки данных из буфера обмена настраивается на этапе разработки компонента выборки. Чтобы система корректно принимала данные, необходимо в методе `onLoadMeta` указать форматы (MIME-типы), которые данная выборка способна обработать.

Система проверяет список `selection.clipboard.pasteMimeTypes` при инициализации выборки и использует его для фильтрации данных при последующих операциях вставки.

```scala
override def onLoadMeta(): Unit = {
  super.onLoadMeta()

  // Указываем поддерживаемые форматы буфера обмена
  // Приоритет обработки: слева направо
  selection.clipboard.pasteMimeTypes = Seq(MimeType.textPlain)
}
```

### Обработка вставки на примере `Btk_Variant`

Копирование и вставка в данном механизме выполняются как два отдельных этапа:

- при копировании источник помещает данные или файлы в буфер обмена;
- при вставке система получает содержимое буфера обмена и запускает его обработку.

Основная логика вставки запускается в методе `onClipboardPaste`. Это обработчик события вставки данных из буфера обмена, который доступен в `AVI` и может быть переопределен в логике выборки.

Объект `event: PasteEvent` содержит:

- `event.file` — файл, в который на сервере преобразовано содержимое буфера обмена;
- `event.type` — тип данных, то есть MIME-type.

При вставке обработка выполняется в таком порядке:

1. Проверяется наличие файла: `event.file != null`.
2. Из файла считываются все строки.
3. Пустые строки отфильтровываются.
4. Если данные есть, запускается дальнейшая логика загрузки.

```scala id="3bbf0h"
override def onClipboardPaste(event: PasteEvent): Unit = {
  dialogs.withInfoForm("Идет загрузка объектов") {
    if (event.file != null) {
      val values = Files.readAllLines(event.file.toPath).asScala
      val convertedValues = values.filter(s => s.ns.isNotNullOrEmpty).toSeq

      if (convertedValues.nonEmpty) {
        if (selection.selectedRecordsCount() === 1 && !convertedValues.head.contains('\t')) {
          super.onClipboardPaste(event)
        } else {
          Btk_VariantObjLib().loadFromClipboard(convertedValues, getMasterId)
        }
      }
    }
  }
  selection.refresh()
}
```

#### Дальнейшая обработка вставленных данных

Метод `loadFromClipboard` определяет, нужно ли создавать новые записи или обновлять существующие.

Если выделенных записей нет или данные не с чем сопоставить, система проходит по всем строкам, полученным при вставке из буфера обмена, и создает новые объекты.

Если в выборке есть выделенные записи, система обновляет существующие объекты значениями из соответствующих строк.

Метод `setLineFromClipboardDataByRop` разбирает одну строку данных в формате TSV (Tab Separated Values) и распределяет значения по атрибутам.

Алгоритм работы:

1. Получает список характеристик варианта, отсортированный по `nOrder`.
2. Разбивает строку по символу табуляции `\t`.
3. Проходит по колонкам.
4. Для каждой колонки находит соответствующий атрибут по индексу.
5. Если значение не пустое, устанавливает его через API.

При установке значения учитывается тип данных:

- для даты при типе `idDate` значение преобразуется заменой `/` на `.` с последующей конвертацией в дату;
- для мультисправочника и интервала значение разбивается по `;`.

Для оптимизации производительности коммит сессии может выполняться пакетно.

```scala id="m7b0xl"
def setLineFromClipboardDataByRop(line: String, idpVariant: NLong, ropVariantObj: Btk_VariantObjApi#ApiRop): Unit = {
  val columns = line.split("\t")

  for (col <- columns.indices) {
    val cellValue = columns(col).trim

    if (col < sortedListUC.length) {
      val attr = sortedListUC(col)
      val idUniversalCharacteristic = attr.get(_.idUniversalCharacteristic)

      if (cellValue.isNotNullOrEmpty) {
        val ucSettings = Btk_UniversalCharacteristicApi().getUniCharSetting(idUniversalCharacteristic)

        val valueToSet =
          if (ucSettings.idDbType === Btk_UniCharDataTypeApi().idDate) {
            cellValue.replace("/", ".").toDate
          } else if (ucSettings.bMultiValue || ucSettings.bInterval) {
            cellValue.split(";")
          } else {
            cellValue
          }

        Btk_VariantObjApi().setUniCharValue(ropVariantObj, idUniversalCharacteristic, valueToSet)
      }
    }
  }
}
```

### Примеры вставки

**Пример 1. Вставка диапазона из Excel**

```excel
A1    B1    C1
A2    B2    C2
```

Передается:

```excel
A1\tB1\tC1
A2\tB2\tC2
```

Система разбивает данные на строки и колонки и сопоставляет их с атрибутами.


**Пример 2. Вставка одного значения**

```excel
TestValue
```

Если выбрана одна запись и нет табуляции, используется стандартная вставка (`super.onClipboardPaste`).


**Пример 3. Преобразование типов**

```excel
01/12/2025    value1;value2
```

Система:

- преобразует дату;
- разбивает значения;
- сохраняет в атрибуты.