Конвертация формата текста при вставке из буффера обмена, часть № 1

"Задачка на 5 мин", но потом выясняется, что внутри — половина Qt, мобильные клавиатуры и немного боли.

На текущем месте работы возникла следующая задачка:


есть кроссплатформенное мобильное приложение, есть многострочное поле ввода TextArea, нужно вставить из буфера rich-text так, чтобы он отображался как plain-text.

Казалось бы, в чём проблема? Устанавливаем свойство textFormat: TextEdit.PlainText, и задача решена, расходимся.Но так получается, что наш TextArea должен работать с rich-text внутри приложения, потому что «так исторически сложилось». А вот если мы хотим вставить какой-то другой rich-text, извне, то его уже нужно вставлять как plain-text.

Хорошо, давайте тогда возьмём QClipboard, подключимся к сигналу changed и будем изменять данные в буфере таким образом, чтобы в нём оказывался только plain-text. Быстро, дерзко, но плохо.


Почему плохо?

Во-первых, это нарушит консистентность буфера в системе: пользователь скопировал rich-text в каком-нибудь редакторе, заглянул в наше приложение, оно изменило буфер, пользователь вернулся в редактор, вставил текст и с удивлением обнаружил, что rich-text превратился в тыкву plain-text. Не очень хорошо, хотя QA могут и не заметить.

Во-вторых, это не работает. Да, вот так просто: слот, подключённый к сигналу changed, не вызывается. По крайней мере, не тогда, когда мы этого ожидаем — ни при активации приложения, ни при непосредственной вставке текста.

Тут важно отметить, что речь идёт об активации пункта Paste в контекстном меню, которое обычно вызывается при долгом нажатии на поле ввода. Связано это с тем, как Qt работает с клавиатурами и полями ввода на мобильных системах (очень сложно и, откровенно говоря, не лучшим образом).

Задачка получается не из простых.


Как нам «перехватить» активацию Paste в контекстном меню?

Раз уж здесь везде QObject, воспользуемся тем, что он умеет — фильтровать события. Для этого открываем документацию по QObject::eventFilter и пишем что-то похожее на пример из документации, но вместо фильтрации просто выводим в stdout все типы событий, которые приходят в наш TextArea.

Дальше пробрасываем экземпляр TextArea в C++ любым удобным способом и устанавливаем на него наш фильтр.

Первое, что бросается в глаза, — это QInputMethodEvent и QInputMethodQueryEvent. Хотя бы потому, что этих событий очень много. Но если покопаться в них, нужных данных вы там не найдёте.

Ладно, отсекаем эти события из вывода в stdout и продолжаем. И вот тут становится интересно.

При активации Paste в контекстном меню в TextArea приходят два события: KeyPress и KeyRelease. Если посмотреть, что это за события подробнее, окажется, что это просто нажатие и отпускание комбинации клавиш Ctrl+V.

Интересный момент: если в этот момент посмотреть на QClipboard, в нём уже будут лежать данные, которые вставляются в поле ввода.

Успех. Нужные данные мы нашли. Но как теперь сказать Qt, что нужно использовать plain-text вместо rich-text?

Можно было бы снова «поколдовать» над QClipboard, но проблема с консистентностью буфера никуда не денется, а это плохой вариант. Значит, нам нужно вызвать ту же логику, которая «под капотом» выполняется при активации Paste, но с использованием plain-text.


Продолжение во 2 части, больше разборов кексов С++ в Telegram

Начать дискуссию