LINUX.ORG.RU

Поле редактирования в Nuklear

 , , ,


0

2

Всех приветствую!

Имеется проблема с работой nk_edit_string, библиотека Nuklear.

Суть: вводим в поле целое число и нажимаем кнопочку рядом, число выводится в консоле (для отладки).

int F=5; // глобальная
...

static char text[64];
static int text_len;
active = nk_edit_string(ctx, NK_EDIT_SIMPLE, text, &text_len, 64, nk_filter_decimal);
            if (nk_button_label(ctx, "->")) // || active & NK_EDIT_COMMITED)
{
   text_len = strlen(text);
   if (text_len !=0 && text_len<8)
   {
       F = atoi(text);
       printf("F=%d\n", F);
       memset(text, '\0', 64);
       text_len=0;
   }
   printf("text=%s\n", text);
}
...

Тест 1: ввели 77, вывод: F=77, text="";

Тест 2: ввели 555, но тут же передумали, стерли и снова набрали 77, вывод: F=775, text="";

Получается буфер text был «555», но был перебит «775», т.е. он не чиститься при каждом новом вводе/редактировании.

Кто-нибудь умеет правильно использовать NK_EDIT_SIMPLE, чтобы не было такого эффекта (вернее сказать, дефекта)?



Последнее исправление: Gyros (всего исправлений: 2)

В общем, 2 года назад я пользовался Nuklear, пользовался и NK_EDIT_SIMPLE. И если честно, проблем не видел

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

И важно, иногда надо просто прогнать анализ еще раз после отрисовки

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

I-Love-Microsoft ★★★★★
()

Каждый доп прогон логики Nuklear он достаточно легковесный, его можно много раз, а вот отрисовку - только 1 раз, иначе огромные очевидные потери в производительности

I-Love-Microsoft ★★★★★
()

static int text_len=63; или static int text_len=0; ? Начальное значение может быть должно быть тоже инициализировано, помимо того что ты отдельно передаёшь размер.

LINUX-ORG-RU ★★★★★
()
Последнее исправление: LINUX-ORG-RU (всего исправлений: 3)

Попробуй так

- text_len = strlen(text);
+ text[text_len] = '\0';

Возможно text[text_len] = '\0'; и не нужно. Но не понятно зачем ты text_len пересчитываешь, его nk_edit_string должен выставить.

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

atoi

Пожалуйста, не используйте эту функцию. У нее нет обработки ошибок. Вариантом немного лучше будет strol, у неё хоть и всё еще есть проблемы, 90% случаев она покроет.

P.S.

--- &text_len, 64
+++ &text_len, sizeof(text)

P.P.S.

{
   text_len = strlen(text);

А нафига вы перезаписываете переменную, которую вам вернул NK? Там реальная длина, а вы берете мусор из буфера

PPP328 ★★★★★
()
Последнее исправление: PPP328 (всего исправлений: 4)

Есть вид nk_edit_string который сам закрывает строку нулем в конце.

NK_API nk_flags nk_edit_string_zero_terminated(struct nk_context*, nk_flags, char *buffer, int max, nk_plugin_filter);

PPP328 написал почему у тебя с твоей функцией есть проблемы.

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

Спасибо PPP328, PRN, LINUX-ORG-RU, MOPKOBKA!

Такой вариант заработал как надо

int F=5; // глобальная
...

static char text[64];
static int text_len;
active = nk_edit_string(ctx, NK_EDIT_SIMPLE, text, &text_len, 64, nk_filter_decimal);
            if (nk_button_label(ctx, "->")) // || active & NK_EDIT_COMMITED)
{
   //text_len = strlen(text);
   if (text_len !=0 && text_len<8)
   {
       F = atoi(text);
       printf("F=%d\n", F);
       //memset(text, '\0', 64); вместо этого
       text[text_len] = '\0';
       //text_len=0;
   }
   printf("text=%s\n", text);
}
...

А почему в примере есть «|| active & NK_EDIT_COMMITED»?

Хотя и работает без этого.

Об atoi(): а какие могут быть ошибки? у меня стоит nk_filter_decimal для поля редактирования, значит только числа. Отрицательные можно отсечь проверив знак, слишком большие - ограничением на text_len.

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

https://www.thecodingfox.com/nuklear-function-reference#nk_edit_string

NK_EDIT_COMMITED - The user pressed Enter to submit the text in the field

if (nk_button_label(ctx, «->») || active & NK_EDIT_COMMITED)

Означает: пользователь или кликнул кнопку или нажал энтер. Но в твоем коде вариант с энтер закомментирован.

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

Можешь представить что active это структура со следующими полями типов bool:

NK_EDIT_ACTIVE - The text field is currently focused
NK_EDIT_INACTIVE - The text field is not focused
NK_EDIT_ACTIVATED - The text field has just received focus
NK_EDIT_DEACTIVATED - The text field has just lost focus
NK_EDIT_COMMITED - The user pressed Enter to submit the text in the field
Проверить, есть ли в active поле NK_EDIT_COMMITED со значением true, можно через
if (active & NK_EDIT_COMMITED) {
  // поле NK_EDIT_COMMITED = true
}
Полей может быть одновременно несколько в состоянии true.

https://dietertack.medium.com/using-bit-flags-in-c-d39ec6e30f08


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

Обрати внимание, что ты вызываешь atoi до установки '\0', делать это нужно после установки '\0', или просто можно воспользоваться функцией которую я подсказал, или скопировать ее поведение.

NK_API nk_flags
nk_edit_string_zero_terminated(struct nk_context *ctx, nk_flags flags,
    char *buffer, int max, nk_plugin_filter filter)
{
    nk_flags result;
    int len = nk_strlen(buffer);
    result = nk_edit_string(ctx, flags, buffer, &len, max, filter);
    buffer[NK_MIN(NK_MAX(max-1,0), len)] = '\0';
    return result;
}
MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 3)
Ответ на: комментарий от Gyros

Об atoi(): а какие могут быть ошибки? у меня стоит nk_filter_decimal для поля редактирования, значит только числа

Никогда не полагайтесь, что вам снаружи прилетит адекватный ввод. Даже если вы считаете, что NK вам отфильтрует.

  • При передаче некорректного ввода atoi вернет 0.
  • При передаче строки «123abc» функция atoi() вернет значение 123, не сообщая о том, что после числа есть недопустимые символы.
  • При передаче строки «999999999999999999999999999999999» функция atoi() вернет значение 2147483647 (максимальное значение для int), что не соответствует действительному числу из-за переполнения.
  • При передаче строки " 123" функция atoi() вернет значение 123, игнорируя пробелы в начале строки.
  • При передаче строки «12.34» функция atoi() вернет значение 12, не учитывая десятичную часть числа.
  • При передаче строки «123abc456» функция atoi() вернет значение 123, игнорируя цифры после букв.

Есть 1000 и 1 способ обмануть поля ввода с фильтром, так что не полагайтесь на это.

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

Спасибо PPP328 за подробный ответ!

Пользуясь Вашим советом, заменил atoi на

F = strtol(text, NULL, 10);

А кстати, обратную операцию (число -> строку) как лучше делать?

Я делаю обычно

char  str_buffer[32]={'\0'};
int N=10;
...
snprintf(str_buffer, 32, "%d", N);

// далее используем str_buffer как нам надо

Есть еще sprintf_s, но она вроде (?) не платформонезависима (майкрософтская реализация); mingw32 ее видит, а gcc - нет

В С++ есть stringstream для таких вещей. И еще также std::snprintf.

Gyros
() автор топика
Ответ на: комментарий от MOPKOBKA

Благодарю MOPKOBKA!

Заменил nk_edit_string на nk_edit_string_zero_terminated

//active = nk_edit_string(ctx, NK_EDIT_SIMPLE, text, &text_len, 64, nk_filter_decimal);
active = nk_edit_string_zero_terminated(ctx, NK_EDIT_SIMPLE, text, 64, nk_filter_decimal);
if (nk_button_label(ctx, "->") || active & NK_EDIT_COMMITED)
{
    F = strtol(text, NULL, 10);
    printf("F=%d\n", F);
    ...
}

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

Теперь еще возник вопрос, а для чего тогда нужен nk_edit_string, если есть nk_edit_string_zero_terminated?

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

заменил atoi на

Не забудьте проверки.

    errno = 0;
    char *endptr;
    int64_t *number = strtoll(string, &endptr, 10);

    if (errno != 0) {
        // Ошибка конвертации, см. errno
    }

    if (*endptr != '\0') {
        // Ошибка конвертации, ввод - не число.
    }

Я делаю обычно

Да, так норм. Особенно с sNprintf вариантом. Но лучше размер не писать руками, можно влететь:

snprintf(buff, sizeof(buff), "%" PRId32, number);

PRI-форматтер для кроссплатформенности, у МС своё представление о long-вариантах и как их форматировать.

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

а для чего тогда нужен nk_edit_string, если есть nk_edit_string_zero_terminated?

Так исторически сложилось, у nk_edit_string неочевидный API и инет завален проблемами как у вас - нет установки \0 вручную.

- 2016/08/08 (1.06.0) - Added `nk_edit_string_zero_terminated` as a second option to
                       `nk_edit_string` which takes, edits and outputs a '\0' terminated string.
PPP328 ★★★★★
()
Ответ на: комментарий от Gyros

Теперь еще возник вопрос, а для чего тогда нужен nk_edit_string, если есть nk_edit_string_zero_terminated?

На случай если тебе не нужна строка которая заканчивается нулем. Но так как скорее всего, тебе нужен ноль, то можно забыть о nk_edit_string.

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

Кстати, обрати внимание, у тебя массив text обозначен как static, поэтому там изначально всегда будут нули, но если бы это был обычный массив, без static, то его хорошо бы инициализировать нулем, text[0] = 0; в самом начале.

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

Но лучше размер не писать руками, можно влететь

Размер форматируемой строки можно получить через

int size = snprintf(NULL, 0, «%» PRId32, number);

Тихо обрезать строку если буфер меньше, это плохо. Может уже обсуждали, я весь тред не читал.

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

Т.е., например, если у меня

char  str_buffer[8]={'\0'};

А number = 1111111111

9 символов, то я узнаю размер

size_t size = snprintf(NULL, 0, «%» PRId32, number);

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

Т.е., что лучше вообще буферы не делать статическими? если обрезка это плохо.

Gyros
() автор топика
Ответ на: комментарий от MOPKOBKA

Да, сделал. Все здорово.

Выводится F в консоль, и по Enter-у, и по кнопочке.

Но у меня перед active=nk_edit_string_zero_terminated стоит

nk_labelf(ctx, NK_TEXT_LEFT, "Number of segments: %d" , F);

Вот он не обновляет F.

PS. F задана как глобальная. Инициализирована каким-то значением по умолчанию.

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

Т.е., что лучше вообще буферы не делать статическими? если обрезка это плохо.

Зависит от ситуации. Можно просто возвращать ошибку, или на верхнем уровне вызывать abort() если буфер слишком маленький.

Можно сделать StringBuilder, он будет хранить в себе строку выделенную через malloc, и увеличить свою длину или сжимать по мере надобности.

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

string в моем примере - это входящая нуль-терминированная строка, там по-барабану, в каком сегменте памяти она находится.

А если вы про buff - то там да, статический буфер, потому что

  1. Только на них работают sizeof
  2. Нет никакого смысла выделять динамически память, а потом с ней бегать и помнить об освобождении, если вы 100% знаете конечный максимальный размер строки.
  3. Занулять его не нужно, это делает snprinf.

Если вы всё-таки про string в первом блоке кода, то повторюсь - это входная строка, там плевать.

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

fprintf(stderr, "Йо, какая-то фигня в строке начиная с %s\n", endptr);
// Или
fprintf(stderr, "Некорректный символ '%c' в строке\n", *endptr); 
PPP328 ★★★★★
()
Ответ на: комментарий от MOPKOBKA

может быть локаль с выводом числа как 1 000 000 000.

  1. Разделителем групп может быть не более, чем один символ
  2. Программа стартует с локалью С => вы знаете, что локаль может быть особенной, потому что сами её меняете.
  3. Буфер в 32 байта всё равно позволяет такую ересь.
PPP328 ★★★★★
()
Ответ на: комментарий от MOPKOBKA

Я вот сейчас специально потратил около получаса, проверяя разные варианты установки разделителя. И таки знаете, что? Он вообще никак не устанавливается. Возможно это артефакт прошлого, возможно нужна Венера в Стрельце проходящая через дырку Юпитера.

Сразу говорю, я не пытался разбираться в вопросе через ман. Я просто перебирал варианты со стаковерлоу.

Я перепробовал:

    struct lconv *ptrLocale = localeconv();
    ptrLocale->decimal_point = ":";
    ptrLocale->thousands_sep = "---";

Тотальный игнор. Даже если ставить один символ.


#include <libintl.h>
...
    bindtextdomain("app", "/usr/share/locale");
    setlocale(LC_NUMERIC, "");
    struct lconv *ptrLocale = localeconv();
    ptrLocale->thousands_sep = strdup("---");

Не работает.

    newloc = newlocale(LC_ALL_MASK, "en_US.UTF-8", (locale_t)0))
    
    ptrLocale = localeconv_l(newloc);
    ptrLocale->thousands_sep[0] = '-';
    ptrLocale->decimal_point[0] = ':';

Оказалось что на моей машине нет localeconv_l, потому что функция не входит в стандарт.

#include <xlocale.h>

Нет такого заголовка на моей машине

    newloc = newlocale(LC_ALL_MASK, "en_US.UTF-8", (locale_t)0)
    
    ptrLocale = localeconv();
    ptrLocale->thousands_sep[0] = '-';
    ptrLocale->decimal_point[0] = ':';
    
    uselocale(newloc);

Это падает с сегфолтом.

    setlocale(LC_ALL, "en_US.UTF-8");
    
    ptrLocale = localeconv();
    ptrLocale->thousands_sep[0] = '-';
    ptrLocale->decimal_point[0] = ':';

Это тоже сегфолт

    char *newThousandsSep = strdup("---");
    free(ptrLocale->thousands_sep);
    ptrLocale->thousands_sep = newThousandsSep;
    ptrLocale->decimal_point = ":";

Сегфолт на том что free получил недопустимый аргумент

    char newThousandsSep[2];
    newThousandsSep[0] = '-';
    newThousandsSep[1] = '\0';
    
    ptrLocale->thousands_sep = newThousandsSep;
    ptrLocale->decimal_point = ":";

Ничего не меняет.

Ну короче, если знаете, как поменять это на свой разделитель и с > 1 байта - дайте знать. Пока я только могу получить пробел и ‘,’, меняя встроенные кодировки.

А ну да, еще в инете предлагают пропатчить glibc.

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

В /usr/share/i18n/locales/ru_RU есть thousands_sep, если поставить два символа, пишет

value for field `thousands_sep' must be a single character
но если поставить русскую ф, то ошибку не выдает, то есть измеряется не в «символах char», а в «символах utf-8».

gcc main.c ; ./a.out
10ф000ф000

Так что не думаю, что об этом стоит задумываться на Win/Lin.

А ну да, еще в инете предлагают пропатчить glibc.

С glibc у меня тоже не удалось в runtime поставить разделители.

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 2)