OmarEmaraDev updated this revision to Diff 353783.
OmarEmaraDev added a comment.

- Remove Field type and use FieldDelegate directly


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,627 @@
   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;
+  }
+
+  void FieldDelegateSetPageIndex(int page_index) { m_page_index = page_index; }
+
+  int FieldDelegateGetPageIndex() { return m_page_index; }
+
+protected:
+  // The index of the page this field belongs to.
+  int m_page_index;
+};
+
+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 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 ? ACS_DIAMOND : ' ');
+    if (is_active)
+      window.AttributeOff(A_REVERSE);
+    window.PutChar(']');
+    window.PutChar(' ');
+    window.PutCString(m_label.c_str());
+  }
+
+  void ToggleContent() { m_content = !m_content; }
+
+  void SetContentToTrue() { m_content = true; }
+
+  void SetContentToFalse() { m_content = false; }
+
+  HandleCharResult FieldDelegateHandleChar(int key) override {
+    switch (key) {
+    case 't':
+    case '1':
+      SetContentToTrue();
+      return eKeyHandled;
+    case 'f':
+    case '0':
+      SetContentToFalse();
+      return eKeyHandled;
+    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 FormDelegate {
+public:
+  FormDelegate() : m_has_error(false), m_last_page_index(0) {}
+
+  virtual ~FormDelegate() = default;
+
+  virtual HandleCharResult FormDelegateHandleChar(int selected_field_index,
+                                                  int key) {
+    return m_fields[selected_field_index]->FieldDelegateHandleChar(key);
+  }
+
+  virtual void FormDelegateDraw(Window &window, int selected_field_index) {
+    int active_page_index = GetActivePageIndex(selected_field_index);
+    for (int i = 0; i < GetNumberOfFields(); i++) {
+      if (m_fields[i]->FieldDelegateGetPageIndex() != active_page_index)
+        continue;
+
+      bool is_field_selected = selected_field_index == i;
+      m_fields[i]->FieldDelegateDraw(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 GetNumberOfFields() { return m_fields.size(); }
+
+  bool HasError() { return m_has_error; }
+
+  std::string &GetError() { return m_error; }
+
+  // Return the index of the page that includes the selected field. If no field
+  // is selected, that is, if the selected field index is not in the correct
+  // range, return the last page index.
+  int GetActivePageIndex(int selected_field_index) {
+    if (selected_field_index < GetNumberOfFields())
+      return m_fields[selected_field_index]->FieldDelegateGetPageIndex();
+    return m_last_page_index;
+  }
+
+  int GetNumberOfPages() { return m_last_page_index + 1; }
+
+  // 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);
+    delegate_sp->FieldDelegateSetPageIndex(m_last_page_index);
+    m_fields.push_back(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);
+    delegate_sp->FieldDelegateSetPageIndex(m_last_page_index);
+    m_fields.push_back(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);
+    delegate_sp->FieldDelegateSetPageIndex(m_last_page_index);
+    m_fields.push_back(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);
+    delegate_sp->FieldDelegateSetPageIndex(m_last_page_index);
+    m_fields.push_back(delegate_sp);
+    return delegate;
+  }
+
+  void NewPage() { m_last_page_index++; }
+
+protected:
+  bool m_has_error;
+  std::string m_error;
+  std::vector<FieldDelegateSP> m_fields;
+  // The index of the last page.
+  int m_last_page_index;
+};
+
+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) {}
+
+  // A form window is divided into two sections. A body section which is padded
+  // by one character from every direction and contains the fields. The body can
+  // have multiple "pages" which are switched automatically as the user selects
+  // next fields. The number of pages is signified by a number of centered dots
+  // at the last line of the body section, the active page will have its dot
+  // highlighted. Additionally a footer section contains the submit button in
+  // one line and a possible error message in the next line. Finally, a
+  // horizontal line separates both sections.
+  //
+  // ___<Form Name>_________________________________________________
+  // |                                                             |
+  // |                                                             |
+  // | Form elements here.                                         |
+  // |                                                             |
+  // |                            ...                              |
+  // |-------------------------------------------------------------|
+  // |                         [ SUBMIT ]                          |
+  // | Error message if it exists.                                 |
+  // |______________________________________[Press Esc to cancel]__|
+  //
+  // The following methods describe this structure in numbers.
+
+  int GetPageIndicatorYLocation(Window &window) {
+    return window.GetHeight() - 5;
+  }
+
+  int GetSeparatorYLocation(Window &window) { return window.GetHeight() - 4; }
+
+  int GetButtonYLocation(Window &window) { return window.GetHeight() - 3; }
+
+  void DrawPageIndicators(Window &window) {
+    int number_of_pages = m_delegate_sp->GetNumberOfPages();
+    if (number_of_pages == 1) {
+      return;
+    }
+    int x = (window.GetWidth() - number_of_pages) / 2;
+    window.MoveCursor(x, GetPageIndicatorYLocation(window));
+    int active_page = m_delegate_sp->GetActivePageIndex(m_selected_field_index);
+    for (int i = 0; i < number_of_pages; i++) {
+      bool is_active = active_page == i;
+      if (is_active)
+        window.AttributeOn(A_REVERSE);
+      window.PutChar(ACS_BULLET);
+      if (is_active)
+        window.AttributeOff(A_REVERSE);
+    }
+  }
+
+  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, GetSeparatorYLocation(window));
+    window.HorizontalLine(window.GetWidth() - 2);
+
+    DrawPageIndicators(window);
+
+    // Draw the centered submit button.
+    const char *button_text = "[Submit]";
+    int x = (window.GetWidth() - sizeof(button_text) - 1) / 2;
+    window.MoveCursor(x, GetButtonYLocation(window));
+    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->GetNumberOfFields();
+    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->GetNumberOfFields();
+    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
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to