From patchwork Sun Oct 27 21:44:24 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Simon Marchi (Code Review)" X-Patchwork-Id: 35376 Received: (qmail 80333 invoked by alias); 27 Oct 2019 21:44:47 -0000 Mailing-List: contact gdb-patches-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: gdb-patches-owner@sourceware.org Delivered-To: mailing list gdb-patches@sourceware.org Received: (qmail 80292 invoked by uid 89); 27 Oct 2019 21:44:46 -0000 Authentication-Results: sourceware.org; auth=none X-Spam-SWARE-Status: No, score=-20.4 required=5.0 tests=AWL, BAYES_00, GIT_PATCH_0, GIT_PATCH_1, GIT_PATCH_2, GIT_PATCH_3 autolearn=ham version=3.3.1 spammy=estate, grew, Horizontal, ensures X-HELO: mx1.osci.io Received: from polly.osci.io (HELO mx1.osci.io) (8.43.85.229) by sourceware.org (qpsmtpd/0.93/v0.84-503-g423c35a) with ESMTP; Sun, 27 Oct 2019 21:44:44 +0000 Received: by mx1.osci.io (Postfix, from userid 994) id E347B2105F; Sun, 27 Oct 2019 17:44:33 -0400 (EDT) Received: from gnutoolchain-gerrit.osci.io (gnutoolchain-gerrit.osci.io [8.43.85.239]) by mx1.osci.io (Postfix) with ESMTP id 0D4F8210AF for ; Sun, 27 Oct 2019 17:44:25 -0400 (EDT) Received: from localhost (localhost [127.0.0.1]) by gnutoolchain-gerrit.osci.io (Postfix) with ESMTP id DF61F2A7D9 for ; Sun, 27 Oct 2019 17:44:24 -0400 (EDT) X-Gerrit-PatchSet: 1 Date: Sun, 27 Oct 2019 17:44:24 -0400 From: "Tom Tromey (Code Review)" To: gdb-patches@sourceware.org Message-ID: Auto-Submitted: auto-generated X-Gerrit-MessageType: newchange Subject: [review] Introduce new layout code X-Gerrit-Change-Id: I3a4cae666327b617d862aaa356f8179f945c6a4e X-Gerrit-Change-Number: 367 X-Gerrit-ChangeURL: X-Gerrit-Commit: dd54abd4e4322f66e4c820a6ef57c61074ade7bf References: Reply-To: tromey@sourceware.org, gdb-patches@sourceware.org MIME-Version: 1.0 Content-Disposition: inline User-Agent: Gerrit/3.0.3-74-g460fb0f7e9 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-10-27 Tom Tromey * 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. Change-Id: I3a4cae666327b617d862aaa356f8179f945c6a4e --- M gdb/ChangeLog M gdb/tui/tui-layout.c M gdb/tui/tui-layout.h 3 files changed, 423 insertions(+), 0 deletions(-) diff --git a/gdb/ChangeLog b/gdb/ChangeLog index 2d14a6f..cb3f0dd 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,5 +1,18 @@ 2019-10-27 Tom Tromey + * 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. + +2019-10-27 Tom Tromey + * 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..f3004ab 100644 --- a/gdb/tui/tui-layout.c +++ b/gdb/tui/tui-layout.c @@ -543,6 +543,284 @@ +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_window::clone () const +{ + tui_layout_window *result = new tui_layout_window (m_contents.c_str ()); + return std::unique_ptr (result); +} + +void +tui_layout_window::apply (int x_, int y_, int width_, int height_) +{ + x = x_; + y = y_; + width = width_; + height = height_; + if (m_window == nullptr) + { + /* This can occur in the special case where the command window + already exists, so get_sizes is skipped. */ + m_window = tui_get_window_by_name (m_contents); + } + 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 (); +} + +tui_layout_split * +tui_layout_split::add_split (int weight) +{ + tui_layout_split *result = new tui_layout_split (); + split s = {weight, std::unique_ptr (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 (result)}; + m_splits.push_back (std::move (s)); +} + +std::unique_ptr +tui_layout_split::clone () const +{ + tui_layout_split *result = new tui_layout_split (); + for (const split &item : m_splits) + { + std::unique_ptr next = item.layout->clone (); + split s = {item.weight, std::move (next)}; + result->m_splits.push_back (std::move (s)); + } + return std::unique_ptr (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; + } +} + +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 heights (m_splits.size ()); + std::vector min_heights (m_splits.size ()); + std::vector 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) + { + if (!m_applied + && strcmp (m_splits[i].layout->get_name (), "cmd") == 0 + && TUI_CMD_WIN != nullptr) + { + /* 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; + available_height -= min_heights[i]; + } + else + { + m_splits[i].layout->get_sizes (&min_heights[i], &max_heights[i]); + if (min_heights[i] == max_heights[i]) + available_height -= min_heights[i]; + else + { + last_index = i; + total_weight += m_splits[i].weight; + } + } + } + + /* 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]; + 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..2af5a77 100644 --- a/gdb/tui/tui-layout.h +++ b/gdb/tui/tui-layout.h @@ -25,6 +25,138 @@ #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 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; + + /* 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 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; + } + +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 clone () const override; + + void apply (int x, int y, int width, int height) override; + + bool adjust_size (const char *name, int new_height) 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 layout; + }; + + /* The splits. */ + std::vector 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);