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

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

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

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.

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

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));
        }
    }
}

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

Примечание: комментарии ниже указывают на фатальный недостаток в коде для этого ответа. Корректировать положение курсора в обработчике события 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.

Другие вопросы по тегам