Introduce new layout code
authorTom Tromey <tom@tromey.com>
Tue, 8 Oct 2019 00:03:02 +0000 (18:03 -0600)
committerTom Tromey <tom@tromey.com>
Wed, 11 Dec 2019 22:49:01 +0000 (15:49 -0700)
This introduces a new approach to window layout for the TUI.  The idea
behind this code is that a layout should be specified in a declarative
way, and then be applied by generic code that does not need to know
the specifics of every possible layout.

This patch itself does not change any behavior, because the new layout
engine isn't yet connected to anything.  That is, this merely
introduces the implementation.

This generic approach makes the code more maintainable.  It also
enables some future changes:

* New window types are simpler to add;
* User-specified layouts are possible; and
* Horizontal layouts are more attainable

gdb/ChangeLog
2019-12-11  Tom Tromey  <tom@tromey.com>

* tui/tui-layout.h (class tui_layout_base)
(class tui_layout_window, class tui_layout_split): New.
* tui/tui-layout.c (tui_get_window_by_name)
(tui_layout_window::clone, tui_layout_window::apply)
(tui_layout_window::get_sizes, tui_layout_window::add_split)
(tui_layout_split::add_window, tui_layout_split::clone)
(tui_layout_split::get_sizes)
(tui_layout_split::set_weights_from_heights)
(tui_layout_split::adjust_size, tui_layout_split::apply): New
functions.
(tui_layout_split::add_split, tui_layout_split::add_split)
(tui_layout_split::set_weights_from_heights)
(tui_layout_split::set_weights_from_heights): New functions.

Change-Id: I3a4cae666327b617d862aaa356f8179f945c6a4e

gdb/ChangeLog
gdb/tui/tui-layout.c
gdb/tui/tui-layout.h

index 7821d2cdd7de17f71b79d4c7186bbd9277a0b28f..595b98743d629a9480a9b08aae0a19e6ba546f96 100644 (file)
@@ -1,3 +1,19 @@
+2019-12-11  Tom Tromey  <tom@tromey.com>
+
+       * tui/tui-layout.h (class tui_layout_base)
+       (class tui_layout_window, class tui_layout_split): New.
+       * tui/tui-layout.c (tui_get_window_by_name)
+       (tui_layout_window::clone, tui_layout_window::apply)
+       (tui_layout_window::get_sizes, tui_layout_window::add_split)
+       (tui_layout_split::add_window, tui_layout_split::clone)
+       (tui_layout_split::get_sizes)
+       (tui_layout_split::set_weights_from_heights)
+       (tui_layout_split::adjust_size, tui_layout_split::apply): New
+       functions.
+       (tui_layout_split::add_split, tui_layout_split::add_split)
+       (tui_layout_split::set_weights_from_heights)
+       (tui_layout_split::set_weights_from_heights): New functions.
+
 2019-12-11  Tom Tromey  <tom@tromey.com>
 
        * tui/tui-wingeneral.c (tui_gen_win_info::make_window): Update.
index 0a7812a53ea5d581939fb06aef21e6add9588e94..91f270dcfc302c03f3b940aaa985ba73576e1dcd 100644 (file)
@@ -543,6 +543,366 @@ show_source_or_disasm_and_command (enum tui_layout_type layout_type)
 
 \f
 
+/* Helper function that returns a TUI window, given its name.  */
+
+static tui_gen_win_info *
+tui_get_window_by_name (const std::string &name)
+{
+  if (name == "src")
+    {
+      if (tui_win_list[SRC_WIN] == nullptr)
+       tui_win_list[SRC_WIN] = new tui_source_window ();
+      return tui_win_list[SRC_WIN];
+    }
+  else if (name == "cmd")
+    {
+      if (tui_win_list[CMD_WIN] == nullptr)
+       tui_win_list[CMD_WIN] = new tui_cmd_window ();
+      return tui_win_list[CMD_WIN];
+    }
+  else if (name == "regs")
+    {
+      if (tui_win_list[DATA_WIN] == nullptr)
+       tui_win_list[DATA_WIN] = new tui_data_window ();
+      return tui_win_list[DATA_WIN];
+    }
+  else if (name == "asm")
+    {
+      if (tui_win_list[DISASSEM_WIN] == nullptr)
+       tui_win_list[DISASSEM_WIN] = new tui_disasm_window ();
+      return tui_win_list[DISASSEM_WIN];
+    }
+  else
+    {
+      gdb_assert (name == "locator");
+      return tui_locator_win_info_ptr ();
+    }
+}
+
+/* See tui-layout.h.  */
+
+std::unique_ptr<tui_layout_base>
+tui_layout_window::clone () const
+{
+  tui_layout_window *result = new tui_layout_window (m_contents.c_str ());
+  return std::unique_ptr<tui_layout_base> (result);
+}
+
+/* See tui-layout.h.  */
+
+void
+tui_layout_window::apply (int x_, int y_, int width_, int height_)
+{
+  x = x_;
+  y = y_;
+  width = width_;
+  height = height_;
+  gdb_assert (m_window != nullptr);
+  m_window->resize (height, width, x, y);
+}
+
+/* See tui-layout.h.  */
+
+void
+tui_layout_window::get_sizes (int *min_height, int *max_height)
+{
+  if (m_window == nullptr)
+    m_window = tui_get_window_by_name (m_contents);
+  *min_height = m_window->min_height ();
+  *max_height = m_window->max_height ();
+}
+
+/* See tui-layout.h.  */
+
+bool
+tui_layout_window::top_boxed_p () const
+{
+  gdb_assert (m_window != nullptr);
+  return m_window->can_box ();
+}
+
+/* See tui-layout.h.  */
+
+bool
+tui_layout_window::bottom_boxed_p () const
+{
+  gdb_assert (m_window != nullptr);
+  return m_window->can_box ();
+}
+
+/* See tui-layout.h.  */
+
+tui_layout_split *
+tui_layout_split::add_split (int weight)
+{
+  tui_layout_split *result = new tui_layout_split ();
+  split s = {weight, std::unique_ptr<tui_layout_base> (result)};
+  m_splits.push_back (std::move (s));
+  return result;
+}
+
+/* See tui-layout.h.  */
+
+void
+tui_layout_split::add_window (const char *name, int weight)
+{
+  tui_layout_window *result = new tui_layout_window (name);
+  split s = {weight, std::unique_ptr<tui_layout_base> (result)};
+  m_splits.push_back (std::move (s));
+}
+
+/* See tui-layout.h.  */
+
+std::unique_ptr<tui_layout_base>
+tui_layout_split::clone () const
+{
+  tui_layout_split *result = new tui_layout_split ();
+  for (const split &item : m_splits)
+    {
+      std::unique_ptr<tui_layout_base> next = item.layout->clone ();
+      split s = {item.weight, std::move (next)};
+      result->m_splits.push_back (std::move (s));
+    }
+  return std::unique_ptr<tui_layout_base> (result);
+}
+
+/* See tui-layout.h.  */
+
+void
+tui_layout_split::get_sizes (int *min_height, int *max_height)
+{
+  *min_height = 0;
+  *max_height = 0;
+  for (const split &item : m_splits)
+    {
+      int new_min, new_max;
+      item.layout->get_sizes (&new_min, &new_max);
+      *min_height += new_min;
+      *max_height += new_max;
+    }
+}
+
+/* See tui-layout.h.  */
+
+bool
+tui_layout_split::top_boxed_p () const
+{
+  if (m_splits.empty ())
+    return false;
+  return m_splits[0].layout->top_boxed_p ();
+}
+
+/* See tui-layout.h.  */
+
+bool
+tui_layout_split::bottom_boxed_p () const
+{
+  if (m_splits.empty ())
+    return false;
+  return m_splits.back ().layout->top_boxed_p ();
+}
+
+/* See tui-layout.h.  */
+
+void
+tui_layout_split::set_weights_from_heights ()
+{
+  for (int i = 0; i < m_splits.size (); ++i)
+    m_splits[i].weight = m_splits[i].layout->height;
+}
+
+/* See tui-layout.h.  */
+
+bool
+tui_layout_split::adjust_size (const char *name, int new_height)
+{
+  /* Look through the children.  If one is a layout holding the named
+     window, we're done; or if one actually is the named window,
+     update it.  */
+  int found_index = -1;
+  for (int i = 0; i < m_splits.size (); ++i)
+    {
+      if (m_splits[i].layout->adjust_size (name, new_height))
+       return true;
+      const char *win_name = m_splits[i].layout->get_name ();
+      if (win_name != nullptr && strcmp (name, win_name) == 0)
+       {
+         found_index = i;
+         break;
+       }
+    }
+
+  if (found_index == -1)
+    return false;
+  if (m_splits[found_index].layout->height == new_height)
+    return true;
+
+  set_weights_from_heights ();
+  int delta = m_splits[found_index].weight - new_height;
+  m_splits[found_index].weight = new_height;
+
+  /* Distribute the "delta" over the next window; but if the next
+     window cannot hold it all, keep going until we either find a
+     window that does, or until we loop all the way around.  */
+  for (int i = 0; delta != 0 && i < m_splits.size () - 1; ++i)
+    {
+      int index = (found_index + 1 + i) % m_splits.size ();
+
+      int new_min, new_max;
+      m_splits[index].layout->get_sizes (&new_min, &new_max);
+
+      if (delta < 0)
+       {
+         /* The primary window grew, so we are trying to shrink other
+            windows.  */
+         int available = m_splits[index].weight - new_min;
+         int shrink_by = std::min (available, -delta);
+         m_splits[index].weight -= shrink_by;
+         delta += shrink_by;
+       }
+      else
+       {
+         /* The primary window shrank, so we are trying to grow other
+            windows.  */
+         int available = new_max - m_splits[index].weight;
+         int grow_by = std::min (available, delta);
+         m_splits[index].weight += grow_by;
+         delta -= grow_by;
+       }
+    }
+
+  if (delta != 0)
+    {
+      warning (_("Invalid window height specified"));
+      /* Effectively undo any modifications made here.  */
+      set_weights_from_heights ();
+    }
+  else
+    {
+      /* Simply re-apply the updated layout.  */
+      apply (x, y, width, height);
+    }
+
+  return true;
+}
+
+/* See tui-layout.h.  */
+
+void
+tui_layout_split::apply (int x_, int y_, int width_, int height_)
+{
+  x = x_;
+  y = y_;
+  width = width_;
+  height = height_;
+
+  struct height_info
+  {
+    int height;
+    int min_height;
+    int max_height;
+    /* True if this window will share a box border with the previous
+       window in the list.  */
+    bool share_box;
+  };
+
+  std::vector<height_info> info (m_splits.size ());
+
+  /* Step 1: Find the min and max height of each sub-layout.
+     Fixed-sized layouts are given their desired height, and then the
+     remaining space is distributed among the remaining windows
+     according to the weights given.  */
+  int available_height = height;
+  int last_index = -1;
+  int total_weight = 0;
+  for (int i = 0; i < m_splits.size (); ++i)
+    {
+      bool cmd_win_already_exists = TUI_CMD_WIN != nullptr;
+
+      /* Always call get_sizes, to ensure that the window is
+        instantiated.  This is a bit gross but less gross than adding
+        special cases for this in other places.  */
+      m_splits[i].layout->get_sizes (&info[i].min_height, &info[i].max_height);
+
+      if (!m_applied
+         && cmd_win_already_exists
+         && m_splits[i].layout->get_name () != nullptr
+         && strcmp (m_splits[i].layout->get_name (), "cmd") == 0)
+       {
+         /* If this layout has never been applied, then it means the
+            user just changed the layout.  In this situation, it's
+            desirable to keep the size of the command window the
+            same.  Setting the min and max heights this way ensures
+            that the resizing step, below, does the right thing with
+            this window.  */
+         info[i].min_height = TUI_CMD_WIN->height;
+         info[i].max_height = TUI_CMD_WIN->height;
+       }
+
+      if (info[i].min_height == info[i].max_height)
+       available_height -= info[i].min_height;
+      else
+       {
+         last_index = i;
+         total_weight += m_splits[i].weight;
+       }
+
+      /* Two adjacent boxed windows will share a border, making a bit
+        more height available.  */
+      if (i > 0
+         && m_splits[i - 1].layout->bottom_boxed_p ()
+         && m_splits[i].layout->top_boxed_p ())
+       info[i].share_box = true;
+    }
+
+  /* Step 2: Compute the height of each sub-layout.  Fixed-sized items
+     are given their fixed size, while others are resized according to
+     their weight.  */
+  int used_height = 0;
+  for (int i = 0; i < m_splits.size (); ++i)
+    {
+      /* Compute the height and clamp to the allowable range.  */
+      info[i].height = available_height * m_splits[i].weight / total_weight;
+      if (info[i].height > info[i].max_height)
+       info[i].height = info[i].max_height;
+      if (info[i].height < info[i].min_height)
+       info[i].height = info[i].min_height;
+      /* If there is any leftover height, just redistribute it to the
+        last resizeable window, by dropping it from the allocated
+        height.  We could try to be fancier here perhaps, by
+        redistributing this height among all windows, not just the
+        last window.  */
+      if (info[i].min_height != info[i].max_height)
+       {
+         used_height += info[i].height;
+         if (info[i].share_box)
+           --used_height;
+       }
+    }
+
+  /* Allocate any leftover height.  */
+  if (available_height >= used_height && last_index != -1)
+    info[last_index].height += available_height - used_height;
+
+  /* Step 3: Resize.  */
+  int height_accum = 0;
+  for (int i = 0; i < m_splits.size (); ++i)
+    {
+      /* If we fall off the bottom, just make allocations overlap.
+        GIGO.  */
+      if (height_accum + info[i].height > height)
+       height_accum = height - info[i].height;
+      else if (info[i].share_box)
+       --height_accum;
+      m_splits[i].layout->apply (x, y + height_accum, width, info[i].height);
+      height_accum += info[i].height;
+    }
+
+  m_applied = true;
+}
+
+\f
+
 /* Function to initialize gdb commands, for tui window layout
    manipulation.  */
 
index 23f05f34aa29e4a7bffbcb37547f23c4423e52ce..d7f0731e312dcced3760b27892cf7d139424b78e 100644 (file)
 #include "tui/tui.h"
 #include "tui/tui-data.h"
 
+/* The basic object in a TUI layout.  This represents a single piece
+   of screen real estate.  Subclasses determine the exact
+   behavior.  */
+class tui_layout_base
+{
+public:
+
+  DISABLE_COPY_AND_ASSIGN (tui_layout_base);
+
+  /* Clone this object.  Ordinarily a layout is cloned before it is
+     used, so that any necessary modifications do not affect the
+     "skeleton" layout.  */
+  virtual std::unique_ptr<tui_layout_base> clone () const = 0;
+
+  /* Change the size and location of this layout.  */
+  virtual void apply (int x, int y, int width, int height) = 0;
+
+  /* Return the minimum and maximum height of this layout.  */
+  virtual void get_sizes (int *min_height, int *max_height) = 0;
+
+  /* True if the topmost item in this layout is boxed.  */
+  virtual bool top_boxed_p () const = 0;
+
+  /* True if the bottommost item in this layout is boxed.  */
+  virtual bool bottom_boxed_p () const = 0;
+
+  /* Return the name of this layout's window, or nullptr if this
+     layout does not represent a single window.  */
+  virtual const char *get_name () const
+  {
+    return nullptr;
+  }
+
+  /* Adjust the size of the window named NAME to NEW_HEIGHT, updating
+     the sizes of the other windows around it.  */
+  virtual bool adjust_size (const char *name, int new_height) = 0;
+
+  /* The most recent space allocation.  */
+  int x = 0;
+  int y = 0;
+  int width = 0;
+  int height = 0;
+
+protected:
+
+  tui_layout_base () = default;
+};
+
+/* A TUI layout object that displays a single window.  The window is
+   given by name.  */
+class tui_layout_window : public tui_layout_base
+{
+public:
+
+  explicit tui_layout_window (const char *name)
+    : m_contents (name)
+  {
+  }
+
+  DISABLE_COPY_AND_ASSIGN (tui_layout_window);
+
+  std::unique_ptr<tui_layout_base> clone () const override;
+
+  void apply (int x, int y, int width, int height) override;
+
+  const char *get_name () const override
+  {
+    return m_contents.c_str ();
+  }
+
+  bool adjust_size (const char *name, int new_height) override
+  {
+    return false;
+  }
+
+  bool top_boxed_p () const override;
+
+  bool bottom_boxed_p () const override;
+
+protected:
+
+  void get_sizes (int *min_height, int *max_height) override;
+
+private:
+
+  /* Type of content to display.  */
+  std::string m_contents;
+
+  /* When a layout is applied, this is updated to point to the window
+     object.  */
+  tui_gen_win_info *m_window = nullptr;
+};
+
+/* A TUI layout that holds other layouts.  */
+class tui_layout_split : public tui_layout_base
+{
+public:
+
+  tui_layout_split () = default;
+
+  DISABLE_COPY_AND_ASSIGN (tui_layout_split);
+
+  /* Add a new split layout to this layout.  WEIGHT is the desired
+     size, which is relative to the other weights given in this
+     layout.  */
+  tui_layout_split *add_split (int weight);
+
+  /* Add a new window to this layout.  NAME is the name of the window
+     to add.  WEIGHT is the desired size, which is relative to the
+     other weights given in this layout.  */
+  void add_window (const char *name, int weight);
+
+  std::unique_ptr<tui_layout_base> clone () const override;
+
+  void apply (int x, int y, int width, int height) override;
+
+  bool adjust_size (const char *name, int new_height) override;
+
+  bool top_boxed_p () const override;
+
+  bool bottom_boxed_p () const override;
+
+protected:
+
+  void get_sizes (int *min_height, int *max_height) override;
+
+private:
+
+  /* Set the weights from the current heights.  */
+  void set_weights_from_heights ();
+
+  struct split
+  {
+    /* The requested weight.  */
+    int weight;
+    /* The layout.  */
+    std::unique_ptr<tui_layout_base> layout;
+  };
+
+  /* The splits.  */
+  std::vector<split> m_splits;
+
+  /* True if this layout has already been applied at least once.  */
+  bool m_applied = false;
+};
+
 extern void tui_add_win_to_layout (enum tui_win_type);
 extern void tui_set_layout (enum tui_layout_type);