LINUX.ORG.RU

Хоткеи и автоповтор

 , ,


0

1

Меня не устраивает один момент. Я не уверен, нужно ли считать это проблемой, а тем более большой проблемой. Однако «это» имеет место быть. И, в целом, если будет возможно отделаться легко — я хотел бы от этого избавиться в своем самописном приложении.

Итак, что же это?

Открываете свой любимый текстовый редактор, открываете в нем файл и нажимаете Ctrl+S и удерживаете нажатым. Что мы видим? Правильно — оно пытается сохраниться множество раз. Причем, если вы подержите подольше — автоповторенные команды накопятся в буфере и будут еще какое-то время выполняться, т.е. вы уже отпустили зажатые клавиши, а оно все еще много раз сохраняется.

Или запустите что-нибудь в vlc, нажмите и удерживайте пробел.

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

Но конкретно в моем случае есть некоторое кол-во мест, где я хотел бы поменять такое поведение.

Но это не возможно сделать просто, когда в пунктах меню у меня работает и клик и хоткеи. Так, мне придется отнаследоваться от QAction, реализовать два-три метода c перехватом ивентов, определять авто-повторы.

Я пробовал игнорить только авто-повторы — этого не достаточно, он пропускает некоторые из них считая что это не повтор, внешне получается эдакий «медленный» авто-повтор. Таким образом нужно блокироваться после первого и не выпускать уже ничего, пока клавиши не отпустят.

Но и это еще не все. Придется перехватить и triggered() сигнал от клика на QAction чтобы можно было быть заблокированным и не выпустить «грязный» сигнал клика во внешний мир или заблокировать и разблокировать по клику.

И вот скажите мне — неужели нет такого кейса в Qt? Я не нагуглил ничего хорошего. Неужели никто никогда не думал про такую необходимость? И насколько это нормально — оборачивать тонны Qt кода своими тоннами кода чтобы побороть стандартное поведение?

Если это нормально — хрен с ним, я смирюсь и реализую.

Но если есть более простой способ достигнуть желаемого — я буду премного благодарен за подсказку!

UPD: Тоже самое хотелось бы решить и для тачпада ноута, т.к. скролл ивент у него отличается от скролл ивента колеса мыши — он еще некоторое время, когда палец уже давно отпущен от скролл-полосы, генерирует уменьшающуюся в амплитуде скролл-дельту для «красоты» smooth-скролла, но мне оно в хрен не впилось.

★★★★★

Последнее исправление: deep-purple (всего исправлений: 1)

Ответ на: комментарий от fluorite

Глянь еще, я UPD добавил.

А что мне даст этот xset? Точнее, почему я должен заставлять юзера крутить какие-то xset (и что там в винде аналогичное?).

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

deep-purple ★★★★★
() автор топика
Ответ на: комментарий от deep-purple

Блокируй кнопку после нажатия, разблокируй после завершения операции. Вообще для долгих операций ui как-то должен показывать, что что-то происходит, показывай хотя бы спиннер, если прогресс-бар нельзя.

fluorite ★★★★★
()
Последнее исправление: fluorite (всего исправлений: 1)
Ответ на: комментарий от fluorite

Т.е. ты предлагаешь всетаки оборачивать критические элементы управления тоннами своего кода. Пусть для кнопки это просто. Для QAction это уже сложнее, я описал это в стартпосте, а для более сложных составных элементов (есть у меня парочка таких) это будет вообще монструозно.

deep-purple ★★★★★
() автор топика
Ответ на: комментарий от fluorite

Любая долгая операция у меня шлет сигнал с состоянием (state | Busy) и есть много способов показать визуально что что-то происходит, в частности, меняется курсор на стрелка+прогресс (колесо/песочные-часы), а после завершения меняется обратно. Суть не в том что я как-то оповещаю пользака о происходящем, а в том, что он может случайно запустить какую-либо операцию несколько раз вместо одного.

deep-purple ★★★★★
() автор топика
Ответ на: комментарий от deep-purple

Вощпе-то никак иначе. А если человек с паркинсоном(или просто пьян, или с бодуна)?
Он может тебе нажать и мышью 2-3 раза.

anonymous
()
Ответ на: комментарий от anonymous

Да то понятно что юзер непредсказуем.

Вот я и спросил в стартпосте дополнительно — можно ли сделать это проще? Если в культях это присутствует под капотом — оборачивать своим велосипедом не придется.

deep-purple ★★★★★
() автор топика

по keydown нужного хоткея устанавливаешь флажок «занято» и запускаешь свой долгоиграющий процесс, если флажок уже установлен - игнорируешь keydown. По окончании долгоиграющего процесса запускаешь таймер, на то время, которое сочтёшь разумным промежутком между долгоиграющими процессами. По срабатыванию таймера сбрасывешь флажок «занято». Элементарно же.

Stanson ★★★★★
()
Ответ на: комментарий от deep-purple

Элементарно с одним флажком, одой кнопкой и одной задачей.

Кто-то запрещает на каждую кнопку по флажку завести, что-ли? С самыми извращёнными хоткеями ну максимум 1000 флажков нужна. Если даже не заморачиваться с atomic bit change, а тупо по байту на кнопку - то в килобайт уложишся. Массив флажков-байт в котором инждексом служит ID хоткея и соответствующей задачи. Нет ничего проще.

Stanson ★★★★★
()
Ответ на: комментарий от Stanson

Задачи в другом треде. Навешивать игноры повторов в «бекенде» — глупо (там все queued). Хранить флажок в каждой кнопке — куча обслуживающего кода.

Перечитай стартпост и скажи есть ли готовый кейс. Или если твой кейс подходит — пояни, ЯННП как его воткнуть.

deep-purple ★★★★★
() автор топика
Ответ на: комментарий от deep-purple

Да какой тут кейс вообще.

2. В начале вызова твоего долгоиграющего процесса пишешь:

if( debounce_flags[ID_CTRL_S] != 0 ) return;
debounce_flags[ID_CTRL_S] = 1;
Да, тут есть вероятность, что после сравнения и до присвоения другой тред уже присвоит 1, но если это выполняется в ответ на нажатие хоткея или там выбора в менюшке, вероятность этого примерно равна нулю и можно со всякими atomic и CAS не заморачиваться.

3. В конце долгоиграющего процесса пишешь что-то типа

start_timer(DEBOUNCE_TIME, debounce_timer_callback, (timer_callback_param_t)ID_CTRL_S)
или как там у тебя таймеры запускаются.

4. В обработчике таймера (я х.з. на чём ты там строчишь и как там это всё выглядит)

debounce_timer_callback( timer_callback_param_t param )
{
debounce_flags[param] = 0;
}

Откуда тут куча обслуживающего кода? Если не нравятся флажки в виде массива байтов и хочется прям 100% thread-safe - замени байты на какие-нибудь мутексы/семафоры/critical_section/что там у тебя есть.

Stanson ★★★★★
()
Ответ на: комментарий от Stanson

В начале вызова твоего долгоиграющего процесса пишешь..

Если я буду писать это в треде, в котором выполняются задачи — все полимеры уже будут просраны. Там не просто тупые задачи, там валидация, которая может пофиксить присланное значение, и/или отпнуть, и/или отправить пофикшенное обратно в интерактивные элементы (вьюхи), там добавление задач в историю изменений, а следовательно, задачи могут быть потомками других задач, там разные задачи могут быть вызваны из разных мест с разными параметрами и поступать по разному в зависимости от этих параметров. С какого перепуга задачи в бекенде должны думать про какой-то там гуй и повторы клавиш? А у меня еще и кли есть, между прочим, и дергает он (последовательно) те же задачи. А может я захочу прикрутить туда дибас или хттп/сокет -сервер для общения с удаленным «гуем» или чем то другим..

Откуда тут куча обслуживающего кода?

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

на чём ты там строчишь

Qt. И ты должен понимать, что отойти от их стандартного апи — собрать тонну граблей. Поэтому я стараюсь манипулировать тем, что доступно там. Подходит ли твое решение?

deep-purple ★★★★★
() автор топика
Ответ на: комментарий от deep-purple

С какого перепуга задачи в бекенде должны думать про какой-то там гуй и повторы клавиш?

Так это какая-то вебня что-ли, а на куте морда для вебни? Так это ничего не меняет.

У тебя в морде на куте где-то есть вызов этого твоего «бэкэнда». И, видимо, обработка результатов работы этого «бэкэнда». Вот прямо перед вызовом «бэкэнда» из морды проверяй флажок - если поднят - нифига не делай, return и всё, если не поднят - поднимай и вызывай «бэкэнд». А там где обрабатываешь ответ «бэкэнда», запускай таймер и по его срабатыванию опускай флажок.

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

Какая тебе разница, какими путями в твоей кутёвой морде дело дошло до вызова «бэкэнда»? Может юзерь контрол мышом дрочит, а не хоткей жмёт. Тебе же надо, чтобы попусту «бэкэнд» не дёргали - это ведь цель, или я что-то не так понял?

Если вызывалки разных функций «бэкэнда» оформлены как классы, то можно вообще приватную переменную как флажок «занятости» использовать.

И ты должен понимать, что отойти от их стандартного апи — собрать тонну граблей.

Да тут вообще апи не при чём. У тебя банальнейшая абстрактная задача подавления дребезга (debounce) некоего сигнала. Чтобы её решить вообще ничего кроме if и операции присваивания не нужно.

Stanson ★★★★★
()
Ответ на: комментарий от Stanson

Так это какая-то вебня что-ли

Типа да.

Тебе же надо, чтобы попусту «бэкэнд» не дёргали

Да, защита от дурака. У бекенда мьютексы, ему похер, там все тип-топ.

ничего кроме if и операции присваивания не нужно

Эммм...

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

А если ждать ответа бекенда, то, если пользак нажал хоткей честно один раз, то второе честное нажатие может быть проигнорировано, т.к. ответ бекенда еще не приехал. И это плохо. Со стороны выглядит как «я нажал, а оно не сработало».

Да, я хочу и рыбку съесть и косточкой не подавиться. Но почему нет, если это будет возможно?

deep-purple ★★★★★
() автор топика
Ответ на: комментарий от deep-purple

Исходя что бекенду по сути похеру, то незачем ждать от него ответа.

Я может отстал от жизни, но я не понимаю, зачем вообще тогда нужен запрос к бэкэнду, если он не отвечает? Типа юзер жмёт на какую-то кнопку, и на этом всё заканчивается? Ни тебе данных от бэкэнда, ни хотя бы «операция успешно выполнена» - в чём тогда смысл этой охоты? Зачем тогда вообще нужен пользователь? Зачем бэкэнд этот? Зачем морда на кутях?

А если ждать ответа бекенда, то, если пользак нажал хоткей честно один раз, то второе честное нажатие может быть проигнорировано, т.к. ответ бекенда еще не приехал. И это плохо. Со стороны выглядит как «я нажал, а оно не сработало».

А как пользователь вообще узнает, что «оно сработало», если не предполагается никакого ответа от бэкэнда? Нет ответа от бэкэнда - значит нет никакой возможности определить, можно ли уже кнопку снова нажимать или нет. С какого перепугу, второе нажатие считается «честным», если нет никаких данных о том, завершилось ли первое нажатие.

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

Stanson ★★★★★
()
Ответ на: комментарий от Stanson

Так «операция выполнена» прилетает. Но как я описал выше — нельзя использовать это событие для сброса блокировки интерактивных элементов, для всех под копирку.

Смотри.

Вьюха хранит «копию» состояния бекенда, но без лишнего. У нас же асинхронщина. Если я переместил мышой какие-то объекты в координатах, то по дроп-событию вьюха шлет сигнал бекенду с нужными данными. Если бекенду все понравилось, он «молчит», если не понравилось — отправляет куда программно переместить объекты, например вернуть в старые координаты, ведь пользак визуально УЖЕ переместил их в новые. Хранить старые координаты во вьюхе — не резон, т.к. это хотя и позволит уменьшить кол-во информации отсылаемой бекендом, но сильно разжирит вьюшные модельки лишними данными и копипастой из логики бекенда. Кроме того есть зависимые модели/вьюхи — изменение в одной, может потребовать фикса значения в другой.

Теперь я трижды нажимаю Ctrl+Z. Заранее известно, что команды выполнятся исключительно последовательно, пусть и с задержкой. С твоим подходом могут не сработать второй и третий. Не беспокойся — ответы придут для каждого из нажатий и в том самом порядке, в итоге вьюха истории пофиксится до актуальной формы, даже если я там бешено контролзечил или если в этот момент я уже перетаскиваю какие-то объекты в новые координаты, они умеют отличать кто их изменяет — бекенд или пользак и все приходит в норму вне зависимости от того жду я окончания работы задачи или ушел вызывать другую, ведь даже удаление объекта не удаляет его по факту, а помещает в историю.

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

Итак. Добавим тестовый sleep в бекенде. Загружаем данные, популируем вьюху, перетаскиваем мышой несколько раз объекты, делаем несколько раз контролзет, затем контролэс, затем контролкью, и все это очень быстро. Что случится? Да ничего плохого — все выполнится в том самом порядке и целостность данных не нарушится.

Теперь выкинь из головы все что ты прочитал.

Единственное что я хочу — при зажатом хоткее отправить сигнал только один раз. Если опираться на ответ бекенда, то визуально будет просто фриз гуя. Тогда смысл в бекенде/экстра-треде вообще?

deep-purple ★★★★★
() автор топика
Ответ на: комментарий от deep-purple

Но как я описал выше — нельзя использовать это событие для сброса блокировки интерактивных элементов, для всех под копирку.

Ну так не делай для всех под копирку, сделай для тех, где критично.

С твоим подходом могут не сработать второй и третий.

Не делай debounce для Ctrl+Z.

Единственное что я хочу — при зажатом хоткее отправить сигнал только один раз.

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

Я не помню как точно хоткеи в qt сделаны, но keydown-keyup там точно есть. keyPressEvent()/keyReleaseEvent, если правильно помню. Виджеты отсубклассить, чтобы keydown-keyup в главное окно прилетали, а там уже разбирай их на хоткеи. Или installEventFilter для каждого виджета и потом лови KeyPress/KeyRelease.

Stanson ★★★★★
()
Ответ на: комментарий от Stanson

Не делай debounce для Ctrl+Z

Он, сука, тоже повторяется, т.к. висит на QAction в меню «Правка». Да даже в окне, все то же самое, возвращаясь к стартпосту — проверка QKeyEvent::isAutoRepeat() не блокирует полностью, пропускает некоторые и делает «медленный» повтор. Возможно это зависит от конкретных настроек DE или WM или иксов, и нет никакого желания в этом разбираться, под кроссплатформ легче решить тут на месте.

Виджеты отсубклассить

Это так же относится к вопросу в стартпосте, а надо ли их субклассить? Может я что-то делаю не так или есть готовое?

deep-purple ★★★★★
() автор топика
Ответ на: комментарий от deep-purple

Да даже в окне, все то же самое, возвращаясь к стартпосту — проверка QKeyEvent::isAutoRepeat() не

А это может косяк в реализации isAutoRepeat. Вообще, например USB клава присылает пакеты только с событиями нажатия и отпускания клавиш. До 6 штук одновремено, что-ли. А автоповтор - это чисто системная фича. И в разных системах оно сделано по-разному. Запросто могли накосячить. Так что сделай себе свой isAutoRepeat из тупого флажка-байта, поднимай по KeyPress, снимай по KeyRelease, если флаг поднят - игнорируй KeyPress.

Может я что-то делаю не так или есть готовое?

Готовое - widget->installEventFilter(this) например.

Stanson ★★★★★
()
Последнее исправление: Stanson (всего исправлений: 1)
Ответ на: комментарий от Stanson

Дык...

Ок, ты меня убедил — надо пилить свое и не париться.

Спасибо! Отмечаю решенной.

Если кто-то осилит весь тред и ему будет что сказать — всегда пожалуйста.

deep-purple ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.