OmarEmaraDev updated this revision to Diff 352778.
OmarEmaraDev added a comment.
- Always scroll left on removing a character
Repository:
rG LLVM Github Monorepo
CHANGES SINCE LAST ACTION
https://reviews.llvm.org/D104395/new/
https://reviews.llvm.org/D104395
Files:
lldb/source/Core/IOHandlerCursesGUI.cpp
Index: lldb/source/Core/IOHandlerCursesGUI.cpp
===================================================================
--- lldb/source/Core/IOHandlerCursesGUI.cpp
+++ lldb/source/Core/IOHandlerCursesGUI.cpp
@@ -392,6 +392,12 @@
void Box(chtype v_char = ACS_VLINE, chtype h_char = ACS_HLINE) {
::box(m_window, v_char, h_char);
}
+ void VerticalLine(int n, chtype v_char = ACS_VLINE) {
+ ::wvline(m_window, v_char, n);
+ }
+ void HorizontalLine(int n, chtype h_char = ACS_HLINE) {
+ ::whline(m_window, h_char, n);
+ }
void Clear() { ::wclear(m_window); }
void Erase() { ::werase(m_window); }
Rect GetBounds() const {
@@ -674,6 +680,36 @@
AttributeOff(attr);
}
+ void DrawBox(const Rect &bounds, chtype v_char = ACS_VLINE,
+ chtype h_char = ACS_HLINE) {
+ MoveCursor(bounds.origin.x, bounds.origin.y);
+ VerticalLine(bounds.size.height);
+ HorizontalLine(bounds.size.width);
+ PutChar(ACS_ULCORNER);
+
+ MoveCursor(bounds.origin.x + bounds.size.width - 1, bounds.origin.y);
+ VerticalLine(bounds.size.height);
+ PutChar(ACS_URCORNER);
+
+ MoveCursor(bounds.origin.x, bounds.origin.y + bounds.size.height - 1);
+ HorizontalLine(bounds.size.width);
+ PutChar(ACS_LLCORNER);
+
+ MoveCursor(bounds.origin.x + bounds.size.width - 1,
+ bounds.origin.y + bounds.size.height - 1);
+ PutChar(ACS_LRCORNER);
+ }
+
+ void DrawTitledBox(const Rect &bounds, const char *title,
+ chtype v_char = ACS_VLINE, chtype h_char = ACS_HLINE) {
+ DrawBox(bounds, v_char, h_char);
+ int title_offset = 2;
+ MoveCursor(bounds.origin.x + title_offset, bounds.origin.y);
+ PutChar('[');
+ PutCString(title, bounds.size.width - title_offset);
+ PutChar(']');
+ }
+
virtual void Draw(bool force) {
if (m_delegate_sp && m_delegate_sp->WindowDelegateDraw(*this, force))
return;
@@ -869,6 +905,550 @@
const Window &operator=(const Window &) = delete;
};
+/////////
+// Forms
+/////////
+
+class FieldDelegate {
+public:
+ virtual ~FieldDelegate() = default;
+
+ virtual Rect FieldDelegateGetBounds() = 0;
+
+ virtual void FieldDelegateDraw(Window &window, bool is_active) = 0;
+
+ virtual HandleCharResult FieldDelegateHandleChar(int key) {
+ return eKeyNotHandled;
+ }
+};
+
+typedef std::shared_ptr<FieldDelegate> FieldDelegateSP;
+
+class TextFieldDelegate : public FieldDelegate {
+public:
+ TextFieldDelegate(const char *label, int width, Point origin,
+ const char *content)
+ : m_label(label), m_width(width), m_origin(origin), m_cursor_position(0),
+ m_first_visibile_char(0) {
+ if (content)
+ m_content = content;
+ assert(m_width > 2);
+ }
+
+ // Get the bounding box of the field. The text field has a height of 3, 2
+ // lines for borders and 1 for the content.
+ Rect FieldDelegateGetBounds() override {
+ return Rect(m_origin, Size(m_width, 3));
+ }
+
+ // Get the start X position of the content in window space, without the
+ // borders.
+ int GetX() { return m_origin.x + 1; }
+
+ // Get the start Y position of the content in window space, without the
+ // borders.
+ int GetY() { return m_origin.y + 1; }
+
+ // Get the effective width of the field, without the borders.
+ int GetEffectiveWidth() { return m_width - 2; }
+
+ // Get the cursor position in window space.
+ int GetCursorWindowXPosition() {
+ return GetX() + m_cursor_position - m_first_visibile_char;
+ }
+
+ int GetContentLength() { return (int)m_content.length(); }
+
+ void FieldDelegateDraw(Window &window, bool is_active) override {
+ // Draw label box.
+ window.DrawTitledBox(FieldDelegateGetBounds(), m_label.c_str());
+
+ // Draw content.
+ window.MoveCursor(GetX(), GetY());
+ const char *text = m_content.c_str() + m_first_visibile_char;
+ window.PutCString(text, GetEffectiveWidth());
+
+ // Highlight the cursor.
+ window.MoveCursor(GetCursorWindowXPosition(), GetY());
+ if (is_active)
+ window.AttributeOn(A_REVERSE);
+ if (m_cursor_position == GetContentLength())
+ // Cursor is past the last character. Highlight an empty space.
+ window.PutChar(' ');
+ else
+ window.PutChar(m_content[m_cursor_position]);
+ if (is_active)
+ window.AttributeOff(A_REVERSE);
+ }
+
+ // The cursor is allowed to move one character past the string.
+ // m_cursor_position is in range [0, GetContentLength()].
+ void MoveCursorRight() {
+ if (m_cursor_position < GetContentLength())
+ m_cursor_position++;
+ }
+
+ void MoveCursorLeft() {
+ if (m_cursor_position > 0)
+ m_cursor_position--;
+ }
+
+ // If the cursor moved past the last visible character, scroll right by one
+ // character.
+ void ScrollRightIfNeeded() {
+ if (m_cursor_position - m_first_visibile_char == GetEffectiveWidth())
+ m_first_visibile_char++;
+ }
+
+ void ScrollLeft() {
+ if (m_first_visibile_char > 0)
+ m_first_visibile_char--;
+ }
+
+ // If the cursor moved past the first visible character, scroll left by one
+ // character.
+ void ScrollLeftIfNeeded() {
+ if (m_cursor_position < m_first_visibile_char)
+ m_first_visibile_char--;
+ }
+
+ // Insert a character at the current cursor position, advance the cursor
+ // position, and make sure to scroll right if needed.
+ void InsertChar(char character) {
+ m_content.insert(m_cursor_position, 1, character);
+ m_cursor_position++;
+ ScrollRightIfNeeded();
+ }
+
+ // Remove the character before the cursor position, retreat the cursor
+ // position, and make sure to scroll left if needed.
+ void RemoveChar() {
+ if (m_cursor_position == 0)
+ return;
+
+ m_content.erase(m_cursor_position - 1, 1);
+ m_cursor_position--;
+ ScrollLeft();
+ }
+
+ // True if the key represents a char that can be inserted in the field
+ // content, false otherwise.
+ virtual bool IsAcceptableChar(int key) { return isprint(key); }
+
+ HandleCharResult FieldDelegateHandleChar(int key) override {
+ if (IsAcceptableChar(key)) {
+ InsertChar((char)key);
+ return eKeyHandled;
+ }
+
+ switch (key) {
+ case KEY_RIGHT:
+ MoveCursorRight();
+ ScrollRightIfNeeded();
+ return eKeyHandled;
+ case KEY_LEFT:
+ MoveCursorLeft();
+ ScrollLeftIfNeeded();
+ return eKeyHandled;
+ case KEY_BACKSPACE:
+ RemoveChar();
+ return eKeyHandled;
+ default:
+ break;
+ }
+ return eKeyNotHandled;
+ }
+
+ // Returns the text content of the field.
+ const std::string &GetText() { return m_content; }
+
+protected:
+ std::string m_label;
+ // The total width of the field, including the two border characters. So the
+ // effective width is two characters less.
+ int m_width;
+ // The position of the top left corner character of the border.
+ Point m_origin;
+ std::string m_content;
+ // The cursor position in the content string itself. Can be in the range
+ // [0, GetContentLength()].
+ int m_cursor_position;
+ // The index of the first visible character in the content.
+ int m_first_visibile_char;
+};
+
+class IntegerFieldDelegate : public TextFieldDelegate {
+public:
+ IntegerFieldDelegate(const char *label, int width, Point origin, int content)
+ : TextFieldDelegate(label, width, origin,
+ std::to_string(content).c_str()) {}
+
+ // Only accept digits.
+ bool IsAcceptableChar(int key) override { return isdigit(key); }
+
+ // Returns the integer content of the field.
+ int GetInteger() { return std::stoi(m_content); }
+};
+
+class BooleanFieldDelegate : public FieldDelegate {
+public:
+ BooleanFieldDelegate(const char *label, Point origin, bool content)
+ : m_label(label), m_origin(origin), m_content(content) {}
+
+ // Get the bounding box of the field. The boolean field is drawn as follows:
+ // [X] Label or [ ] Label
+ // So 4 characters plus the length of the label. And only a single line.
+ Rect FieldDelegateGetBounds() override {
+ return Rect(m_origin, Size(4 + m_label.length(), 1));
+ }
+
+ // [X] Label or [ ] Label
+ void FieldDelegateDraw(Window &window, bool is_active) override {
+ window.MoveCursor(m_origin.x, m_origin.y);
+ window.PutChar('[');
+ if (is_active)
+ window.AttributeOn(A_REVERSE);
+ window.PutChar(m_content ? 'X' : ' ');
+ if (is_active)
+ window.AttributeOff(A_REVERSE);
+ window.PutChar(']');
+ window.PutChar(' ');
+ window.PutCString(m_label.c_str());
+ }
+
+ void ToggleContent() { m_content = !m_content; }
+
+ HandleCharResult FieldDelegateHandleChar(int key) override {
+ switch (key) {
+ case ' ':
+ case '\r':
+ case '\n':
+ case KEY_ENTER:
+ ToggleContent();
+ return eKeyHandled;
+ default:
+ break;
+ }
+ return eKeyNotHandled;
+ }
+
+ // Returns the boolean content of the field.
+ bool GetBoolean() { return m_content; }
+
+protected:
+ std::string m_label;
+ // The window space position of the first character.
+ Point m_origin;
+ bool m_content;
+};
+
+class ChoicesFieldDelegate : public FieldDelegate {
+public:
+ ChoicesFieldDelegate(const char *label, int width, int height, Point origin,
+ std::vector<std::string> choices)
+ : m_label(label), m_width(width), m_height(height), m_origin(origin),
+ m_choices(choices), m_choice(0), m_first_visibile_choice(0) {
+ assert(m_width > 3);
+ assert(m_height > 2);
+ }
+
+ // Get the bounding box of the field. The height is 2 border characters with
+ // one or more space to show choices in a list.
+ Rect FieldDelegateGetBounds() override {
+ return Rect(m_origin, Size(m_width, m_height));
+ }
+
+ // Get the X position of the choices in window space, without the borders.
+ int GetX() { return m_origin.x + 1; }
+
+ // Get the Y position of the first visible choice in window space.
+ int GetY() { return m_origin.y + 1; }
+
+ // Get the effective width of the field, without the borders.
+ int GetEffectiveWidth() { return m_width - 2; }
+ //
+ // Get the effective height of the field, without the borders.
+ int GetEffectiveHeight() { return m_height - 2; }
+
+ int GetNumberOfChoices() { return m_choices.size(); }
+
+ // Get the index of the last visible choice.
+ int GetLastVisibleChoice() {
+ return std::min(m_first_visibile_choice + GetEffectiveHeight() - 1,
+ GetNumberOfChoices() - 1);
+ }
+
+ void FieldDelegateDraw(Window &window, bool is_active) override {
+ // Draw label box.
+ window.DrawTitledBox(FieldDelegateGetBounds(), m_label.c_str());
+
+ // Draw content.
+ int choices_to_draw = GetLastVisibleChoice() - m_first_visibile_choice + 1;
+ for (int i = 0; i < choices_to_draw; i++) {
+ window.MoveCursor(GetX(), GetY() + i);
+ int current_choice = m_first_visibile_choice + i;
+ const char *text = m_choices[current_choice].c_str();
+ bool highlight = is_active && current_choice == m_choice;
+ if (highlight)
+ window.AttributeOn(A_REVERSE);
+ window.PutChar(current_choice == m_choice ? ACS_DIAMOND : ' ');
+ window.PutCString(text);
+ if (highlight)
+ window.AttributeOff(A_REVERSE);
+ }
+ }
+
+ void SelectPrevious() {
+ if (m_choice > 0)
+ m_choice--;
+ }
+
+ void SelectNext() {
+ if (m_choice < GetNumberOfChoices() - 1)
+ m_choice++;
+ }
+
+ // If the cursor moved past the first visible choice, scroll up by one
+ // choice.
+ void ScrollUpIfNeeded() {
+ if (m_choice < m_first_visibile_choice)
+ m_first_visibile_choice--;
+ }
+
+ // If the cursor moved past the last visible choice, scroll down by one
+ // choice.
+ void ScrollDownIfNeeded() {
+ if (m_choice > GetLastVisibleChoice())
+ m_first_visibile_choice++;
+ }
+
+ HandleCharResult FieldDelegateHandleChar(int key) override {
+ switch (key) {
+ case KEY_UP:
+ SelectPrevious();
+ ScrollUpIfNeeded();
+ return eKeyHandled;
+ case KEY_DOWN:
+ SelectNext();
+ ScrollDownIfNeeded();
+ return eKeyHandled;
+ default:
+ break;
+ }
+ return eKeyNotHandled;
+ }
+
+ // Returns the content of the choice as a string.
+ std::string GetChoiceContent() { return m_choices[m_choice]; }
+
+ // Returns the index of the choice.
+ int GetChoice() { return m_choice; }
+
+protected:
+ std::string m_label;
+ // The total width of the field, including the two border characters. So the
+ // effective width is two characters less.
+ int m_width;
+ // The total height of the field, including the two border characters. So the
+ // effective width is two characters less.
+ int m_height;
+ // The position of the top left corner character of the border.
+ Point m_origin;
+ std::vector<std::string> m_choices;
+ // The index of the selected choice.
+ int m_choice;
+ // The index of the first visible choice in the field.
+ int m_first_visibile_choice;
+};
+
+class Field {
+public:
+ Field(FieldDelegateSP &delegate_sp) : m_delegate_sp(delegate_sp) {}
+
+ void Draw(Window &window, bool is_active) {
+ m_delegate_sp->FieldDelegateDraw(window, is_active);
+ }
+
+ HandleCharResult HandleChar(int key) {
+ return m_delegate_sp->FieldDelegateHandleChar(key);
+ }
+
+protected:
+ FieldDelegateSP m_delegate_sp;
+};
+
+class FormDelegate {
+public:
+ FormDelegate() : m_has_error(false) {}
+
+ virtual ~FormDelegate() = default;
+
+ virtual HandleCharResult FormDelegateHandleChar(int selected_field_index,
+ int key) {
+ return m_fields[selected_field_index].HandleChar(key);
+ }
+
+ virtual void FormDelegateDraw(Window &window, int selected_field_index) {
+ for (int i = 0; i < FormDelegateGetNumberOfFields(); i++) {
+ bool is_field_selected = selected_field_index == i;
+ m_fields[i].Draw(window, is_field_selected);
+ }
+ }
+
+ // Return true if submission was successful, false otherwise. If false, the
+ // method should set the m_error member to an appropriate error message.
+ virtual bool FormDelegateSubmit() = 0;
+
+ int FormDelegateGetNumberOfFields() { return m_fields.size(); }
+
+ bool HasError() { return m_has_error; }
+
+ std::string &GetError() { return m_error; }
+
+ // Factory methods to create and add fields of specific types.
+
+ TextFieldDelegate *AddTextField(const char *label, int width, Point origin,
+ const char *content) {
+ TextFieldDelegate *delegate =
+ new TextFieldDelegate(label, width, origin, content);
+ FieldDelegateSP delegate_sp = FieldDelegateSP(delegate);
+ m_fields.push_back(Field(delegate_sp));
+ return delegate;
+ }
+
+ IntegerFieldDelegate *AddIntegerField(const char *label, int width,
+ Point origin, int content) {
+ IntegerFieldDelegate *delegate =
+ new IntegerFieldDelegate(label, width, origin, content);
+ FieldDelegateSP delegate_sp = FieldDelegateSP(delegate);
+ m_fields.push_back(Field(delegate_sp));
+ return delegate;
+ }
+
+ BooleanFieldDelegate *AddBooleanField(const char *label, Point origin,
+ bool content) {
+ BooleanFieldDelegate *delegate =
+ new BooleanFieldDelegate(label, origin, content);
+ FieldDelegateSP delegate_sp = FieldDelegateSP(delegate);
+ m_fields.push_back(Field(delegate_sp));
+ return delegate;
+ }
+
+ ChoicesFieldDelegate *AddChoicesField(const char *label, int width,
+ int height, Point origin,
+ std::vector<std::string> choices) {
+ ChoicesFieldDelegate *delegate =
+ new ChoicesFieldDelegate(label, width, height, origin, choices);
+ FieldDelegateSP delegate_sp = FieldDelegateSP(delegate);
+ m_fields.push_back(Field(delegate_sp));
+ return delegate;
+ }
+
+protected:
+ bool m_has_error;
+ std::string m_error;
+ std::vector<Field> m_fields;
+};
+
+typedef std::shared_ptr<FormDelegate> FormDelegateSP;
+
+class FormWindowDelegate : public WindowDelegate {
+public:
+ FormWindowDelegate(FormDelegateSP &delegate_sp)
+ : m_delegate_sp(delegate_sp), m_selected_field_index(0) {}
+
+ bool WindowDelegateDraw(Window &window, bool force) override {
+ window.Erase();
+
+ window.DrawTitleBox(window.GetName(), "Press Esc to cancel");
+
+ // Draw field elements.
+ m_delegate_sp->FormDelegateDraw(window, m_selected_field_index);
+
+ // Draw a horizontal line separating the fields and the submit button.
+ window.MoveCursor(1, window.GetHeight() - 4);
+ window.HorizontalLine(window.GetWidth() - 2);
+
+ // Draw the centered submit button.
+ const char *button_text = "[Submit]";
+ int x = (window.GetWidth() - sizeof(button_text) - 1) / 2;
+ window.MoveCursor(x, window.GetHeight() - 3);
+ if (IsButtonActive())
+ window.AttributeOn(A_REVERSE);
+ window.PutCString(button_text);
+ if (IsButtonActive())
+ window.AttributeOff(A_REVERSE);
+
+ // Draw the error if it exists.
+ if (m_delegate_sp->HasError()) {
+ window.MoveCursor(2, window.GetHeight() - 2);
+ window.AttributeOn(COLOR_PAIR(RedOnBlack));
+ window.PutChar(ACS_DIAMOND);
+ window.PutChar(' ');
+ window.PutCStringTruncated(1, m_delegate_sp->GetError().c_str());
+ window.AttributeOff(COLOR_PAIR(RedOnBlack));
+ }
+
+ return true;
+ }
+
+ // The index can be equal to the number of fields, hence the plus one. See
+ // IsButtonActive().
+ void SelectedNextField() {
+ m_selected_field_index++;
+ int number_of_fields = m_delegate_sp->FormDelegateGetNumberOfFields();
+ m_selected_field_index %= number_of_fields + 1;
+ }
+
+ void SubmitForm(Window &window) {
+ bool is_successful = m_delegate_sp->FormDelegateSubmit();
+ if (is_successful)
+ window.GetParent()->RemoveSubWindow(&window);
+ }
+
+ HandleCharResult WindowDelegateHandleChar(Window &window, int key) override {
+ switch (key) {
+ case '\r':
+ case '\n':
+ case KEY_ENTER:
+ if (IsButtonActive()) {
+ SubmitForm(window);
+ return eKeyHandled;
+ }
+ break;
+ case '\t':
+ SelectedNextField();
+ return eKeyHandled;
+ case KEY_ESCAPE:
+ window.GetParent()->RemoveSubWindow(&window);
+ return eKeyHandled;
+ default:
+ break;
+ }
+
+ // If the key wasn't handled and one of the fields is active, pass the key
+ // to that field.
+ if (!IsButtonActive()) {
+ return m_delegate_sp->FormDelegateHandleChar(m_selected_field_index, key);
+ }
+
+ return eKeyNotHandled;
+ }
+
+ // When the selected field index is equal to the number of selected fields,
+ // this denotes that the submit button is selected.
+ bool IsButtonActive() {
+ int number_of_fields = m_delegate_sp->FormDelegateGetNumberOfFields();
+ return m_selected_field_index == number_of_fields;
+ }
+
+protected:
+ FormDelegateSP m_delegate_sp;
+ // The index of the selected field. This can be equal to the number of fields,
+ // in which case, it denotes that the submit button is selected.
+ int m_selected_field_index;
+};
+
class MenuDelegate {
public:
virtual ~MenuDelegate() = default;
_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits