Как управлять LineIndentation в wxStyledTextCtrl, когда пользователь нажимает Enter

Когда пользователь нажимает клавишу Enter в wxStyledTextCtrl, кажется, что курсор всегда перемещается в начало строки (нулевой отступ), что, скорее всего, является ожидаемым поведением.

Я хочу иметь возможность писать код сценария в следующем формате с отступами строки.

for i=1,10 do --say there is no indentation
   i=i+1 -- now there is indentation via tab key
   -- pressing enter should proceed with this level of indentation
   print(i) -- same level of indentation with the previous code line
end

Я использую следующий код C++, чтобы иметь возможность управлять отступами на самом базовом уровне.

void Script::OnKeyUp(wxKeyEvent& evt)
{
    if ((evt.GetKeyCode() == WXK_RETURN || evt.GetKeyCode() == WXK_NUMPAD_ENTER)) {
        long int col, line;
        PositionToXY(GetInsertionPoint(), &col, &line);
        int PreviousIndentation = GetLineIndentation(line-1);
        SetLineIndentation(line, PreviousIndentation);
        GotoPos(GetCurrentPos() + PreviousIndentation);
    }
}

Приведенный выше код C++ сохраняет уровень отступа, однако курсор сначала перемещается в начало строки, а затем на уровень отступа. При использовании других IDE этого не происходит, например, переход к началу строки, а затем к уровню отступа. Вместо этого курсор сразу переходит на /следует за уровнем отступа. Есть ли способ, которым курсор может сразу перейти на уровень отступа, не переходя изначально на нулевой уровень отступа.

Кстати, я попробовал EVT_STC_CHARADDED, который вроде как реализован в ZeroBraneStudio, но при нажатии клавиши Enter evt.GetKeyCode() возвращает странное целое число, а evt.GetUnicodeKey возвращает \0 и, кроме того, событие EVT_STC_CHARADDED вызывается дважды (думаю, из-за CR+LF).

Кстати, я использую wxWidgets-3.1.0 в Windows 10.

Любые идеи были бы хорошы.


person macroland    schedule 15.02.2017    source источник


Ответы (2)


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

  • wxEVT_CHAR_HOOK
  • wxEVT_KEY_DOWN
  • wxEVT_STC_MODIFIED — Тип модификации: 0x00100000
  • wxEVT_STC_MODIFIED — Тип модификации: 0x00000410
  • wxEVT_STC_MODIFIED — Тип модификации: 0x00002011
  • wxEVT_STC_CHARADDED
  • wxEVT_STC_UPDATEUI
  • wxEVT_STC_PAINTED
  • wxEVT_KEY_UP

С событиями char_hook и key_down ключ еще не был отправлен элементу управления, поэтому он не сможет предоставить необходимую информацию о положении. Элемент управления не должен быть изменен в событии stc_modified, поэтому мы не должны использовать эти события. К моменту события stc_painted курсор уже нарисован, так что это и событие key_up слишком поздно. И в другом ответе я узнал, что событие stc_updateui не сработает.

Таким образом, методом исключения единственной возможностью является событие wxEVT_STC_CHARADDED. Следующий вопрос — что делать в этом обработчике событий. Я адаптировал код из здесь для работы с wxStyledTextCtrl.

void Script::OnCharAdded(wxStyledTextEvent& event)
{
    int new_line_key=(GetEOLMode()==wxSTC_EOL_CR)?13:10;

    if ( event.GetKey() == new_line_key )
    {
        int cur_pos = GetCurrentPos();
        int cur_line = LineFromPosition(cur_pos);

        if ( cur_line > 0 )
        {
            wxString prev_line = GetLine(cur_line-1);
            size_t prev_line_indent_chars(0);
            for ( size_t i=0; i<prev_line.Length(); ++i )
            {
                wxUniChar cur_char=prev_line.GetChar(i);

                if (cur_char==' ')
                {
                    ++prev_line_indent_chars;
                }
                else if (cur_char=='\t')
                {
                    ++prev_line_indent_chars;
                }
                else
                {
                    break;
                }
            }

            AddText(prev_line.Left(prev_line_indent_chars));
        }
    }
}

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

person New Pagodi    schedule 18.02.2017
comment
Большое спасибо! Вчера я потратил пару часов, чтобы решить проблему пропуска строки, но безрезультатно. Теперь он работает так, как должен работать. - person macroland; 19.02.2017
comment
Извините за другой ответ. Как только я понял проблему, я потратил около 3 часов, пытаясь обойти ее, прежде чем понял, что это, вероятно, безнадежно. - person New Pagodi; 19.02.2017
comment
С вашими недавними улучшениями в ответе, теперь он действительно очень хороший! - person macroland; 20.02.2017

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


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

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

void Script::OnModified(wxStyledTextEvent& event)
{
    int mt = event.GetModificationType();

    if(mt&wxSTC_MOD_INSERTTEXT && mt&wxSTC_PERFORMED_USER && event.GetLinesAdded()==1)
    {
        int cur_line = m_stc->LineFromPosition(event.GetPosition());
        int cur_indent = m_stc->GetLineIndentation(cur_line);
        m_indentToAdd=cur_indent;
    }
}

Чтобы курсор не начинался с начала строки, а затем перемещался к отступу, вы можете обработать событие wxEVT_STC_UPDATEUI и сбросить позицию там:

void Script::OnUpdateUI(wxStyledTextEvent& event)
{
    if(m_indentToAdd)
    {
        int cur_pos = m_stc->GetCurrentPos();
        int cur_line = m_stc->LineFromPosition(cur_pos);
        m_stc->SetLineIndentation(cur_line, m_indentToAdd);
        m_stc->GotoPos(cur_pos+m_indentToAdd);

        m_indentToAdd=0;
    }
}

Событие UpdateUI не предоставляет текущую позицию или строку, поэтому их необходимо пересчитать, прежде чем можно будет установить отступ. Я предполагаю, что это можно оптимизировать, сохранив эти значения в обработчике событий OnModified, а затем используя их в обработчике событий UpdateUI.

person New Pagodi    schedule 15.02.2017
comment
Спасибо за хороший и по существу ответ! Это работает хорошо, за исключением того, что если под строкой, которую пользователь нажмет, есть пустая строка, например, пользователь находится на 5-й строке, а 6-я строка пуста (скажем, в редакторе есть 6-я строка), тогда отступ следует из 7-й строки а не 6-й. Если бы не было 6-й строки, нажатие Enter делает то, что должно делать. Я должен найти способ исправить это. Только один вопрос: я вижу, что вы используете LineFromIndentation, а не GetLineIndentation; есть ли для этого конкретная причина? - person macroland; 16.02.2017
comment
Решение, которое я дал, было очень мало протестировано, но я не вижу проблемы из-за пустых следующих строк. Для меня это работает во всех следующих случаях: нет следующей строки, пустая следующая строка и следующая строка не пуста, но с другим уровнем отступа. Если вы не можете найти исправление, можете ли вы дать более подробную информацию? - person New Pagodi; 16.02.2017
comment
Также на ваш последний вопрос: вы спрашивали, почему я использую LineFromPosition вместо PositionToXY? Нет никакой реальной причины. PositionToXY реализован с использованием LineFromPosition, поэтому они оба делают одно и то же. - person New Pagodi; 16.02.2017
comment
Я использую следующий код: int cur_pos = GetCurrentPos(); int cur_line = LineFromPosition(cur_pos); SetLineIndentation(cur_line, m_indentToAdd); int NextLinePos = XYToPosition(m_indentToAdd, cur_line + 1); GotoPos(NextLinePos); Например, когда я впервые нажимаю ввод во 2-й строке, где есть отступ, значения равны cur_line=2; NextLinePos=16. Значит работает корректно и переходит на 3-ю строчку. Теперь, если я использую клавишу со стрелкой вверх, чтобы вернуться ко 2-й строке, и снова нажмите клавишу ввода cur_line=2; NextLinePos=18. Не уверен, что происходит. - person macroland; 18.02.2017
comment
Спасибо. Теперь я вижу проблему - и из-за этого этот ответ просто не сработает. Кажется, что когда вы пытаетесь сбросить позицию курсора, в событии UpdateUI обновляется не вся информация о позиции. Как вы описали выше, когда вы переходите с 3-й строки обратно на 2-ю, курсор должен перейти на четвертую позицию в строке, но поскольку я испортил информацию о позиции в событии UpdateUI, вместо этого он переходит на первую позицию. Я опубликую новый ответ, который работает лучше (или, по крайней мере, не имеет этой проблемы). - person New Pagodi; 19.02.2017