[review,v2] Introduce new layout code

Message ID 20191106142740.BFB812816A@gnutoolchain-gerrit.osci.io
State Superseded
Headers show
Series
  • [review,v2] Introduce new layout code
Related show

Commit Message

Sourceware to Gerrit sync (Code Review) Nov. 6, 2019, 2:27 p.m.
Change URL: https://gnutoolchain-gerrit.osci.io/r/c/binutils-gdb/+/367
......................................................................

Introduce new layout code

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 the 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-11-06  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
---
M gdb/ChangeLog
M gdb/tui/tui-layout.c
M gdb/tui/tui-layout.h
3 files changed, 479 insertions(+), 0 deletions(-)




-- 
Gerrit-Project: binutils-gdb
Gerrit-Branch: master
Gerrit-Change-Id: I3a4cae666327b617d862aaa356f8179f945c6a4e
Gerrit-Change-Number: 367
Gerrit-PatchSet: 2
Gerrit-Owner: Tom Tromey <tromey@sourceware.org>
Gerrit-MessageType: newpatchset

Patch

diff --git a/gdb/ChangeLog b/gdb/ChangeLog
index 41e214d..3b3bbb2 100644
--- a/gdb/ChangeLog
+++ b/gdb/ChangeLog
@@ -1,5 +1,21 @@ 
 2019-11-06  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-11-06  Tom Tromey  <tom@tromey.com>
+
 	* tui/tui-wingeneral.c (tui_gen_win_info::make_window): Update.
 	* tui/tui-win.c (tui_adjust_win_heights, tui_resize_all): Update.
 	* tui/tui-layout.c (tui_gen_win_info::resize)
diff --git a/gdb/tui/tui-layout.c b/gdb/tui/tui-layout.c
index 0a7812a..11a0eb9 100644
--- a/gdb/tui/tui-layout.c
+++ b/gdb/tui/tui-layout.c
@@ -543,6 +543,323 @@ 
 
 
 
+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 ();
+    }
+}
+
+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);
+}
+
+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);
+}
+
+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 ();
+}
+
+bool
+tui_layout_window::top_boxed_p () const
+{
+  gdb_assert (m_window != nullptr);
+  return m_window->can_box ();
+}
+
+bool
+tui_layout_window::bottom_boxed_p () const
+{
+  gdb_assert (m_window != nullptr);
+  return m_window->can_box ();
+}
+
+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;
+}
+
+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));
+}
+
+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);
+}
+
+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;
+    }
+}
+
+bool
+tui_layout_split::top_boxed_p () const
+{
+  if (m_splits.empty ())
+    return false;
+  return m_splits[0].layout->top_boxed_p ();
+}
+
+bool
+tui_layout_split::bottom_boxed_p () const
+{
+  if (m_splits.empty ())
+    return false;
+  return m_splits.back ().layout->top_boxed_p ();
+}
+
+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;
+}
+
+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;
+}
+
+void
+tui_layout_split::apply (int x_, int y_, int width_, int height_)
+{
+  x = x_;
+  y = y_;
+  width = width_;
+  height = height_;
+
+  std::vector<int> heights (m_splits.size ());
+  std::vector<int> min_heights (m_splits.size ());
+  std::vector<int> max_heights (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 (&min_heights[i], &max_heights[i]);
+
+      if (!m_applied
+	  && cmd_win_already_exists
+	  && 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.  */
+	  min_heights[i] = TUI_CMD_WIN->height;
+	  max_heights[i] = TUI_CMD_WIN->height;
+	}
+
+      if (min_heights[i] == max_heights[i])
+	available_height -= min_heights[i];
+      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 ())
+	++available_height;
+    }
+
+  /* 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)
+    {
+      heights[i] = available_height * m_splits[i].weight / total_weight;
+      /* 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 (heights[i] > max_heights[i])
+	heights[i] = max_heights[i];
+      if (heights[i] < min_heights[i])
+	heights[i] = min_heights[i];
+      used_height += heights[i];
+    }
+
+  /* Allocate any leftover height.  */
+  if (available_height >= used_height && last_index != -1)
+    heights[last_index] += 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 + heights[i] > height)
+	height_accum = height - heights[i];
+      else if (i > 0
+	       && m_splits[i - 1].layout->bottom_boxed_p ()
+	       && m_splits[i].layout->top_boxed_p ())
+	--height_accum;
+      m_splits[i].layout->apply (x, y + height_accum, width, heights[i]);
+      height_accum += heights[i];
+    }
+
+  m_applied = true;
+}
+
+
+
 /* Function to initialize gdb commands, for tui window layout
    manipulation.  */
 
diff --git a/gdb/tui/tui-layout.h b/gdb/tui/tui-layout.h
index 23f05f3..d7f0731 100644
--- a/gdb/tui/tui-layout.h
+++ b/gdb/tui/tui-layout.h
@@ -25,6 +25,152 @@ 
 #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);