]> git.ipfire.org Git - thirdparty/binutils-gdb.git/blob - gdb/tui/tui-layout.c
Update copyright year range in header of all files managed by GDB
[thirdparty/binutils-gdb.git] / gdb / tui / tui-layout.c
1 /* TUI layout window management.
2
3 Copyright (C) 1998-2023 Free Software Foundation, Inc.
4
5 Contributed by Hewlett-Packard Company.
6
7 This file is part of GDB.
8
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 3 of the License, or
12 (at your option) any later version.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>. */
21
22 #include "defs.h"
23 #include "arch-utils.h"
24 #include "command.h"
25 #include "symtab.h"
26 #include "frame.h"
27 #include "source.h"
28 #include "cli/cli-cmds.h"
29 #include "cli/cli-decode.h"
30 #include "cli/cli-utils.h"
31 #include <ctype.h>
32 #include <unordered_map>
33 #include <unordered_set>
34
35 #include "tui/tui.h"
36 #include "tui/tui-command.h"
37 #include "tui/tui-data.h"
38 #include "tui/tui-wingeneral.h"
39 #include "tui/tui-stack.h"
40 #include "tui/tui-regs.h"
41 #include "tui/tui-win.h"
42 #include "tui/tui-winsource.h"
43 #include "tui/tui-disasm.h"
44 #include "tui/tui-layout.h"
45 #include "tui/tui-source.h"
46 #include "gdb_curses.h"
47 #include "safe-ctype.h"
48
49 static void extract_display_start_addr (struct gdbarch **, CORE_ADDR *);
50
51 /* The layouts. */
52 static std::vector<std::unique_ptr<tui_layout_split>> layouts;
53
54 /* The layout that is currently applied. */
55 static std::unique_ptr<tui_layout_base> applied_layout;
56
57 /* The "skeleton" version of the layout that is currently applied. */
58 static tui_layout_split *applied_skeleton;
59
60 /* The two special "regs" layouts. Note that these aren't registered
61 as commands and so can never be deleted. */
62 static tui_layout_split *src_regs_layout;
63 static tui_layout_split *asm_regs_layout;
64
65 /* See tui-data.h. */
66 std::vector<tui_win_info *> tui_windows;
67
68 /* See tui-layout.h. */
69
70 void
71 tui_apply_current_layout (bool preserve_cmd_win_size_p)
72 {
73 struct gdbarch *gdbarch;
74 CORE_ADDR addr;
75
76 extract_display_start_addr (&gdbarch, &addr);
77
78 for (tui_win_info *win_info : tui_windows)
79 win_info->make_visible (false);
80
81 applied_layout->apply (0, 0, tui_term_width (), tui_term_height (),
82 preserve_cmd_win_size_p);
83
84 /* Keep the list of internal windows up-to-date. */
85 for (int win_type = SRC_WIN; (win_type < MAX_MAJOR_WINDOWS); win_type++)
86 if (tui_win_list[win_type] != nullptr
87 && !tui_win_list[win_type]->is_visible ())
88 tui_win_list[win_type] = nullptr;
89
90 /* This should always be made visible by a layout. */
91 gdb_assert (TUI_CMD_WIN != nullptr);
92 gdb_assert (TUI_CMD_WIN->is_visible ());
93
94 /* Get the new list of currently visible windows. */
95 std::vector<tui_win_info *> new_tui_windows;
96 applied_layout->get_windows (&new_tui_windows);
97
98 /* Now delete any window that was not re-applied. */
99 tui_win_info *focus = tui_win_with_focus ();
100 for (tui_win_info *win_info : tui_windows)
101 {
102 if (!win_info->is_visible ())
103 {
104 if (focus == win_info)
105 tui_set_win_focus_to (new_tui_windows[0]);
106 delete win_info;
107 }
108 }
109
110 /* Replace the global list of active windows. */
111 tui_windows = std::move (new_tui_windows);
112
113 if (gdbarch == nullptr && TUI_DISASM_WIN != nullptr)
114 tui_get_begin_asm_address (&gdbarch, &addr);
115 tui_update_source_windows_with_addr (gdbarch, addr);
116 }
117
118 /* See tui-layout. */
119
120 void
121 tui_adjust_window_height (struct tui_win_info *win, int new_height)
122 {
123 applied_layout->set_height (win->name (), new_height);
124 }
125
126 /* See tui-layout. */
127
128 void
129 tui_adjust_window_width (struct tui_win_info *win, int new_width)
130 {
131 applied_layout->set_width (win->name (), new_width);
132 }
133
134 /* Set the current layout to LAYOUT. */
135
136 static void
137 tui_set_layout (tui_layout_split *layout)
138 {
139 std::string old_fingerprint;
140 if (applied_layout != nullptr)
141 old_fingerprint = applied_layout->layout_fingerprint ();
142
143 applied_skeleton = layout;
144 applied_layout = layout->clone ();
145
146 std::string new_fingerprint = applied_layout->layout_fingerprint ();
147 bool preserve_command_window_size
148 = (TUI_CMD_WIN != nullptr && old_fingerprint == new_fingerprint);
149
150 tui_apply_current_layout (preserve_command_window_size);
151 }
152
153 /* See tui-layout.h. */
154
155 void
156 tui_add_win_to_layout (enum tui_win_type type)
157 {
158 gdb_assert (type == SRC_WIN || type == DISASSEM_WIN);
159
160 /* If the window already exists, no need to add it. */
161 if (tui_win_list[type] != nullptr)
162 return;
163
164 /* If the window we are trying to replace doesn't exist, we're
165 done. */
166 enum tui_win_type other = type == SRC_WIN ? DISASSEM_WIN : SRC_WIN;
167 if (tui_win_list[other] == nullptr)
168 return;
169
170 const char *name = type == SRC_WIN ? SRC_NAME : DISASSEM_NAME;
171 applied_layout->replace_window (tui_win_list[other]->name (), name);
172 tui_apply_current_layout (true);
173 }
174
175 /* Find LAYOUT in the "layouts" global and return its index. */
176
177 static size_t
178 find_layout (tui_layout_split *layout)
179 {
180 for (size_t i = 0; i < layouts.size (); ++i)
181 {
182 if (layout == layouts[i].get ())
183 return i;
184 }
185 gdb_assert_not_reached ("layout not found!?");
186 }
187
188 /* Function to set the layout. */
189
190 static void
191 tui_apply_layout (const char *args, int from_tty, cmd_list_element *command)
192 {
193 tui_layout_split *layout = (tui_layout_split *) command->context ();
194
195 /* Make sure the curses mode is enabled. */
196 tui_enable ();
197 tui_set_layout (layout);
198 }
199
200 /* See tui-layout.h. */
201
202 void
203 tui_next_layout ()
204 {
205 size_t index = find_layout (applied_skeleton);
206 ++index;
207 if (index == layouts.size ())
208 index = 0;
209 tui_set_layout (layouts[index].get ());
210 }
211
212 /* Implement the "layout next" command. */
213
214 static void
215 tui_next_layout_command (const char *arg, int from_tty)
216 {
217 tui_enable ();
218 tui_next_layout ();
219 }
220
221 /* See tui-layout.h. */
222
223 void
224 tui_set_initial_layout ()
225 {
226 tui_set_layout (layouts[0].get ());
227 }
228
229 /* Implement the "layout prev" command. */
230
231 static void
232 tui_prev_layout_command (const char *arg, int from_tty)
233 {
234 tui_enable ();
235 size_t index = find_layout (applied_skeleton);
236 if (index == 0)
237 index = layouts.size ();
238 --index;
239 tui_set_layout (layouts[index].get ());
240 }
241
242
243 /* See tui-layout.h. */
244
245 void
246 tui_regs_layout ()
247 {
248 /* If there's already a register window, we're done. */
249 if (TUI_DATA_WIN != nullptr)
250 return;
251
252 tui_set_layout (TUI_DISASM_WIN != nullptr
253 ? asm_regs_layout
254 : src_regs_layout);
255 }
256
257 /* Implement the "layout regs" command. */
258
259 static void
260 tui_regs_layout_command (const char *arg, int from_tty)
261 {
262 tui_enable ();
263 tui_regs_layout ();
264 }
265
266 /* See tui-layout.h. */
267
268 void
269 tui_remove_some_windows ()
270 {
271 tui_win_info *focus = tui_win_with_focus ();
272
273 if (strcmp (focus->name (), CMD_NAME) == 0)
274 {
275 /* Try leaving the source or disassembly window. If neither
276 exists, just do nothing. */
277 focus = TUI_SRC_WIN;
278 if (focus == nullptr)
279 focus = TUI_DISASM_WIN;
280 if (focus == nullptr)
281 return;
282 }
283
284 applied_layout->remove_windows (focus->name ());
285 tui_apply_current_layout (true);
286 }
287
288 static void
289 extract_display_start_addr (struct gdbarch **gdbarch_p, CORE_ADDR *addr_p)
290 {
291 if (TUI_SRC_WIN != nullptr)
292 TUI_SRC_WIN->display_start_addr (gdbarch_p, addr_p);
293 else if (TUI_DISASM_WIN != nullptr)
294 TUI_DISASM_WIN->display_start_addr (gdbarch_p, addr_p);
295 else
296 {
297 *gdbarch_p = nullptr;
298 *addr_p = 0;
299 }
300 }
301
302 void
303 tui_win_info::resize (int height_, int width_,
304 int origin_x_, int origin_y_)
305 {
306 if (width == width_ && height == height_
307 && x == origin_x_ && y == origin_y_
308 && handle != nullptr)
309 return;
310
311 width = width_;
312 height = height_;
313 x = origin_x_;
314 y = origin_y_;
315
316 if (handle != nullptr)
317 {
318 #ifdef HAVE_WRESIZE
319 wresize (handle.get (), height, width);
320 mvwin (handle.get (), y, x);
321 wmove (handle.get (), 0, 0);
322 #else
323 handle.reset (nullptr);
324 #endif
325 }
326
327 if (handle == nullptr)
328 make_window ();
329
330 rerender ();
331 }
332
333 \f
334
335 /* Helper function to create one of the built-in (non-locator)
336 windows. */
337
338 template<enum tui_win_type V, class T>
339 static tui_win_info *
340 make_standard_window (const char *)
341 {
342 if (tui_win_list[V] == nullptr)
343 tui_win_list[V] = new T ();
344 return tui_win_list[V];
345 }
346
347 /* A map holding all the known window types, keyed by name. Note that
348 this is heap-allocated and "leaked" at gdb exit. This avoids
349 ordering issues with destroying elements in the map at shutdown.
350 In particular, destroying this map can occur after Python has been
351 shut down, causing crashes if any window destruction requires
352 running Python code. */
353
354 static std::unordered_map<std::string, window_factory> *known_window_types;
355
356 /* Helper function that returns a TUI window, given its name. */
357
358 static tui_win_info *
359 tui_get_window_by_name (const std::string &name)
360 {
361 for (tui_win_info *window : tui_windows)
362 if (name == window->name ())
363 return window;
364
365 auto iter = known_window_types->find (name);
366 if (iter == known_window_types->end ())
367 error (_("Unknown window type \"%s\""), name.c_str ());
368
369 tui_win_info *result = iter->second (name.c_str ());
370 if (result == nullptr)
371 error (_("Could not create window \"%s\""), name.c_str ());
372 return result;
373 }
374
375 /* Initialize the known window types. */
376
377 static void
378 initialize_known_windows ()
379 {
380 known_window_types = new std::unordered_map<std::string, window_factory>;
381
382 known_window_types->emplace (SRC_NAME,
383 make_standard_window<SRC_WIN,
384 tui_source_window>);
385 known_window_types->emplace (CMD_NAME,
386 make_standard_window<CMD_WIN, tui_cmd_window>);
387 known_window_types->emplace (DATA_NAME,
388 make_standard_window<DATA_WIN,
389 tui_data_window>);
390 known_window_types->emplace (DISASSEM_NAME,
391 make_standard_window<DISASSEM_WIN,
392 tui_disasm_window>);
393 known_window_types->emplace (STATUS_NAME,
394 make_standard_window<STATUS_WIN,
395 tui_locator_window>);
396 }
397
398 /* See tui-layout.h. */
399
400 void
401 tui_register_window (const char *name, window_factory &&factory)
402 {
403 std::string name_copy = name;
404
405 if (name_copy == SRC_NAME || name_copy == CMD_NAME || name_copy == DATA_NAME
406 || name_copy == DISASSEM_NAME || name_copy == STATUS_NAME)
407 error (_("Window type \"%s\" is built-in"), name);
408
409 for (const char &c : name_copy)
410 {
411 if (ISSPACE (c))
412 error (_("invalid whitespace character in window name"));
413
414 if (!ISALNUM (c) && strchr ("-_.", c) == nullptr)
415 error (_("invalid character '%c' in window name"), c);
416 }
417
418 if (!ISALPHA (name_copy[0]))
419 error (_("window name must start with a letter, not '%c'"), name_copy[0]);
420
421 known_window_types->emplace (std::move (name_copy),
422 std::move (factory));
423 }
424
425 /* See tui-layout.h. */
426
427 std::unique_ptr<tui_layout_base>
428 tui_layout_window::clone () const
429 {
430 tui_layout_window *result = new tui_layout_window (m_contents.c_str ());
431 return std::unique_ptr<tui_layout_base> (result);
432 }
433
434 /* See tui-layout.h. */
435
436 void
437 tui_layout_window::apply (int x_, int y_, int width_, int height_,
438 bool preserve_cmd_win_size_p)
439 {
440 x = x_;
441 y = y_;
442 width = width_;
443 height = height_;
444 gdb_assert (m_window != nullptr);
445 m_window->resize (height, width, x, y);
446 }
447
448 /* See tui-layout.h. */
449
450 void
451 tui_layout_window::get_sizes (bool height, int *min_value, int *max_value)
452 {
453 TUI_SCOPED_DEBUG_ENTER_EXIT;
454
455 if (m_window == nullptr)
456 m_window = tui_get_window_by_name (m_contents);
457
458 tui_debug_printf ("window = %s, getting %s",
459 m_window->name (), (height ? "height" : "width"));
460
461 if (height)
462 {
463 *min_value = m_window->min_height ();
464 *max_value = m_window->max_height ();
465 }
466 else
467 {
468 *min_value = m_window->min_width ();
469 *max_value = m_window->max_width ();
470 }
471
472 tui_debug_printf ("min = %d, max = %d", *min_value, *max_value);
473 }
474
475 /* See tui-layout.h. */
476
477 bool
478 tui_layout_window::first_edge_has_border_p () const
479 {
480 gdb_assert (m_window != nullptr);
481 return m_window->can_box ();
482 }
483
484 /* See tui-layout.h. */
485
486 bool
487 tui_layout_window::last_edge_has_border_p () const
488 {
489 gdb_assert (m_window != nullptr);
490 return m_window->can_box ();
491 }
492
493 /* See tui-layout.h. */
494
495 void
496 tui_layout_window::replace_window (const char *name, const char *new_window)
497 {
498 if (m_contents == name)
499 {
500 m_contents = new_window;
501 if (m_window != nullptr)
502 {
503 m_window->make_visible (false);
504 m_window = tui_get_window_by_name (m_contents);
505 }
506 }
507 }
508
509 /* See tui-layout.h. */
510
511 void
512 tui_layout_window::specification (ui_file *output, int depth)
513 {
514 gdb_puts (get_name (), output);
515 }
516
517 /* See tui-layout.h. */
518
519 std::string
520 tui_layout_window::layout_fingerprint () const
521 {
522 if (strcmp (get_name (), "cmd") == 0)
523 return "C";
524 else
525 return "";
526 }
527
528 /* See tui-layout.h. */
529
530 void
531 tui_layout_split::add_split (std::unique_ptr<tui_layout_split> &&layout,
532 int weight)
533 {
534 split s = {weight, std::move (layout)};
535 m_splits.push_back (std::move (s));
536 }
537
538 /* See tui-layout.h. */
539
540 void
541 tui_layout_split::add_window (const char *name, int weight)
542 {
543 tui_layout_window *result = new tui_layout_window (name);
544 split s = {weight, std::unique_ptr<tui_layout_base> (result)};
545 m_splits.push_back (std::move (s));
546 }
547
548 /* See tui-layout.h. */
549
550 std::unique_ptr<tui_layout_base>
551 tui_layout_split::clone () const
552 {
553 tui_layout_split *result = new tui_layout_split (m_vertical);
554 for (const split &item : m_splits)
555 {
556 std::unique_ptr<tui_layout_base> next = item.layout->clone ();
557 split s = {item.weight, std::move (next)};
558 result->m_splits.push_back (std::move (s));
559 }
560 return std::unique_ptr<tui_layout_base> (result);
561 }
562
563 /* See tui-layout.h. */
564
565 void
566 tui_layout_split::get_sizes (bool height, int *min_value, int *max_value)
567 {
568 TUI_SCOPED_DEBUG_ENTER_EXIT;
569
570 *min_value = 0;
571 *max_value = 0;
572 bool first_time = true;
573 for (const split &item : m_splits)
574 {
575 int new_min, new_max;
576 item.layout->get_sizes (height, &new_min, &new_max);
577 /* For the mismatch case, the first time through we want to set
578 the min and max to the computed values -- the "first_time"
579 check here is just a funny way of doing that. */
580 if (height == m_vertical || first_time)
581 {
582 *min_value += new_min;
583 *max_value += new_max;
584 }
585 else
586 {
587 *min_value = std::max (*min_value, new_min);
588 *max_value = std::min (*max_value, new_max);
589 }
590 first_time = false;
591 }
592
593 tui_debug_printf ("min_value = %d, max_value = %d", *min_value, *max_value);
594 }
595
596 /* See tui-layout.h. */
597
598 bool
599 tui_layout_split::first_edge_has_border_p () const
600 {
601 if (m_splits.empty ())
602 return false;
603 return m_splits[0].layout->first_edge_has_border_p ();
604 }
605
606 /* See tui-layout.h. */
607
608 bool
609 tui_layout_split::last_edge_has_border_p () const
610 {
611 if (m_splits.empty ())
612 return false;
613 return m_splits.back ().layout->last_edge_has_border_p ();
614 }
615
616 /* See tui-layout.h. */
617
618 void
619 tui_layout_split::set_weights_from_sizes ()
620 {
621 for (int i = 0; i < m_splits.size (); ++i)
622 m_splits[i].weight
623 = m_vertical ? m_splits[i].layout->height : m_splits[i].layout->width;
624 }
625
626 /* See tui-layout.h. */
627
628 std::string
629 tui_layout_split::tui_debug_weights_to_string () const
630 {
631 std::string str;
632
633 for (int i = 0; i < m_splits.size (); ++i)
634 {
635 if (i > 0)
636 str += ", ";
637 str += string_printf ("[%d] %d", i, m_splits[i].weight);
638 }
639
640 return str;
641 }
642
643 /* See tui-layout.h. */
644
645 void
646 tui_layout_split::tui_debug_print_size_info
647 (const std::vector<tui_layout_split::size_info> &info)
648 {
649 gdb_assert (debug_tui);
650
651 tui_debug_printf ("current size info data:");
652 for (int i = 0; i < info.size (); ++i)
653 tui_debug_printf (" [%d] { size = %d, min = %d, max = %d, share_box = %d }",
654 i, info[i].size, info[i].min_size,
655 info[i].max_size, info[i].share_box);
656 }
657
658 /* See tui-layout.h. */
659
660 tui_adjust_result
661 tui_layout_split::set_size (const char *name, int new_size, bool set_width_p)
662 {
663 TUI_SCOPED_DEBUG_ENTER_EXIT;
664
665 tui_debug_printf ("this = %p, name = %s, new_size = %d",
666 this, name, new_size);
667
668 /* Look through the children. If one is a layout holding the named
669 window, we're done; or if one actually is the named window,
670 update it. */
671 int found_index = -1;
672 for (int i = 0; i < m_splits.size (); ++i)
673 {
674 tui_adjust_result adjusted;
675 if (set_width_p)
676 adjusted = m_splits[i].layout->set_width (name, new_size);
677 else
678 adjusted = m_splits[i].layout->set_height (name, new_size);
679 if (adjusted == HANDLED)
680 return HANDLED;
681 if (adjusted == FOUND)
682 {
683 if (set_width_p ? m_vertical : !m_vertical)
684 return FOUND;
685 found_index = i;
686 break;
687 }
688 }
689
690 if (found_index == -1)
691 return NOT_FOUND;
692 int curr_size = (set_width_p
693 ? m_splits[found_index].layout->width
694 : m_splits[found_index].layout->height);
695 if (curr_size == new_size)
696 return HANDLED;
697
698 tui_debug_printf ("found window %s at index %d", name, found_index);
699
700 set_weights_from_sizes ();
701 int delta = m_splits[found_index].weight - new_size;
702 m_splits[found_index].weight = new_size;
703
704 tui_debug_printf ("before delta (%d) distribution, weights: %s",
705 delta, tui_debug_weights_to_string ().c_str ());
706
707 /* Distribute the "delta" over all other windows, while respecting their
708 min/max sizes. We grow each window by 1 line at a time continually
709 looping over all the windows. However, skip the window that the user
710 just resized, obviously we don't want to readjust that window. */
711 bool found_window_that_can_grow_p = true;
712 for (int i = 0; delta != 0; i = (i + 1) % m_splits.size ())
713 {
714 int index = (found_index + 1 + i) % m_splits.size ();
715 if (index == found_index)
716 {
717 if (!found_window_that_can_grow_p)
718 break;
719 found_window_that_can_grow_p = false;
720 continue;
721 }
722
723 int new_min, new_max;
724 m_splits[index].layout->get_sizes (m_vertical, &new_min, &new_max);
725
726 if (delta < 0)
727 {
728 /* The primary window grew, so we are trying to shrink other
729 windows. */
730 if (m_splits[index].weight > new_min)
731 {
732 m_splits[index].weight -= 1;
733 delta += 1;
734 found_window_that_can_grow_p = true;
735 }
736 }
737 else
738 {
739 /* The primary window shrank, so we are trying to grow other
740 windows. */
741 if (m_splits[index].weight < new_max)
742 {
743 m_splits[index].weight += 1;
744 delta -= 1;
745 found_window_that_can_grow_p = true;
746 }
747 }
748
749 tui_debug_printf ("index = %d, weight now: %d",
750 index, m_splits[index].weight);
751 }
752
753 tui_debug_printf ("after delta (%d) distribution, weights: %s",
754 delta, tui_debug_weights_to_string ().c_str ());
755
756 if (delta != 0)
757 {
758 if (set_width_p)
759 warning (_("Invalid window width specified"));
760 else
761 warning (_("Invalid window height specified"));
762 /* Effectively undo any modifications made here. */
763 set_weights_from_sizes ();
764 }
765 else
766 {
767 /* Simply re-apply the updated layout. We pass false here so that
768 the cmd window can be resized. However, we should have already
769 resized everything above to be "just right", so the apply call
770 here should not end up changing the sizes at all. */
771 apply (x, y, width, height, false);
772 }
773
774 return HANDLED;
775 }
776
777 /* See tui-layout.h. */
778
779 void
780 tui_layout_split::apply (int x_, int y_, int width_, int height_,
781 bool preserve_cmd_win_size_p)
782 {
783 TUI_SCOPED_DEBUG_ENTER_EXIT;
784
785 x = x_;
786 y = y_;
787 width = width_;
788 height = height_;
789
790 /* In some situations we fix the size of the cmd window. However,
791 occasionally this turns out to be a mistake. This struct is used to
792 hold the original information about the cmd window, so we can restore
793 it if needed. */
794 struct old_size_info
795 {
796 /* Constructor. */
797 old_size_info (int index_, int min_size_, int max_size_)
798 : index (index_),
799 min_size (min_size_),
800 max_size (max_size_)
801 { /* Nothing. */ }
802
803 /* The index in m_splits where the cmd window was found. */
804 int index;
805
806 /* The previous min/max size. */
807 int min_size;
808 int max_size;
809 };
810
811 /* This is given a value only if we fix the size of the cmd window. */
812 gdb::optional<old_size_info> old_cmd_info;
813
814 std::vector<size_info> info (m_splits.size ());
815
816 tui_debug_printf ("weights are: %s",
817 tui_debug_weights_to_string ().c_str ());
818
819 /* Step 1: Find the min and max size of each sub-layout.
820 Fixed-sized layouts are given their desired size, and then the
821 remaining space is distributed among the remaining windows
822 according to the weights given. */
823 int available_size = m_vertical ? height : width;
824 int last_index = -1;
825 int total_weight = 0;
826 for (int i = 0; i < m_splits.size (); ++i)
827 {
828 bool cmd_win_already_exists = TUI_CMD_WIN != nullptr;
829
830 /* Always call get_sizes, to ensure that the window is
831 instantiated. This is a bit gross but less gross than adding
832 special cases for this in other places. */
833 m_splits[i].layout->get_sizes (m_vertical, &info[i].min_size,
834 &info[i].max_size);
835
836 if (preserve_cmd_win_size_p
837 && cmd_win_already_exists
838 && m_splits[i].layout->get_name () != nullptr
839 && strcmp (m_splits[i].layout->get_name (), "cmd") == 0)
840 {
841 /* Save the old cmd window information, in case we need to
842 restore it later. */
843 old_cmd_info.emplace (i, info[i].min_size, info[i].max_size);
844
845 /* If this layout has never been applied, then it means the
846 user just changed the layout. In this situation, it's
847 desirable to keep the size of the command window the
848 same. Setting the min and max sizes this way ensures
849 that the resizing step, below, does the right thing with
850 this window. */
851 info[i].min_size = (m_vertical
852 ? TUI_CMD_WIN->height
853 : TUI_CMD_WIN->width);
854 info[i].max_size = info[i].min_size;
855 }
856
857 if (info[i].min_size == info[i].max_size)
858 available_size -= info[i].min_size;
859 else
860 {
861 last_index = i;
862 total_weight += m_splits[i].weight;
863 }
864
865 /* Two adjacent boxed windows will share a border, making a bit
866 more size available. */
867 if (i > 0
868 && m_splits[i - 1].layout->last_edge_has_border_p ()
869 && m_splits[i].layout->first_edge_has_border_p ())
870 info[i].share_box = true;
871 }
872
873 /* If last_index is set then we have a window that is not of a fixed
874 size. This window will have its size calculated below, which requires
875 that the total_weight not be zero (we divide by total_weight, so don't
876 want a floating-point exception). */
877 gdb_assert (last_index == -1 || total_weight > 0);
878
879 /* Step 2: Compute the size of each sub-layout. Fixed-sized items
880 are given their fixed size, while others are resized according to
881 their weight. */
882 int used_size = 0;
883 for (int i = 0; i < m_splits.size (); ++i)
884 {
885 if (info[i].min_size != info[i].max_size)
886 {
887 /* Compute the height and clamp to the allowable range. */
888 info[i].size = available_size * m_splits[i].weight / total_weight;
889 if (info[i].size > info[i].max_size)
890 info[i].size = info[i].max_size;
891 if (info[i].size < info[i].min_size)
892 info[i].size = info[i].min_size;
893 /* Keep a total of all the size we've used so far (we gain some
894 size back if this window can share a border with a preceding
895 window). Any unused space will be distributed between all of
896 the other windows (while respecting min/max sizes) later in
897 this function. */
898 used_size += info[i].size;
899 if (info[i].share_box)
900 --used_size;
901 }
902 else
903 info[i].size = info[i].min_size;
904 }
905
906 if (debug_tui)
907 {
908 tui_debug_printf ("after initial size calculation");
909 tui_debug_printf ("available_size = %d, used_size = %d",
910 available_size, used_size);
911 tui_debug_printf ("total_weight = %d, last_index = %d",
912 total_weight, last_index);
913 tui_debug_print_size_info (info);
914 }
915
916 /* If we didn't find any sub-layouts that were of a non-fixed size, but
917 we did find the cmd window, then we can consider that a sort-of
918 non-fixed size sub-layout.
919
920 The cmd window might, initially, be of a fixed size (see above), but,
921 we are willing to relax this constraint if required to correctly apply
922 this layout (see below). */
923 if (last_index == -1 && old_cmd_info.has_value ())
924 last_index = old_cmd_info->index;
925
926 /* Allocate any leftover size. */
927 if (available_size != used_size && last_index != -1)
928 {
929 /* Loop over all windows until the amount of used space is equal to
930 the amount of available space. There's an escape hatch within
931 the loop in case we can't find any sub-layouts to resize. */
932 bool found_window_that_can_grow_p = true;
933 for (int idx = last_index;
934 available_size != used_size;
935 idx = (idx + 1) % m_splits.size ())
936 {
937 /* Every time we get back to last_index, which is where the loop
938 started, we check to make sure that we did assign some space
939 to a window, bringing used_size closer to available_size.
940
941 If we didn't, but the cmd window is of a fixed size, then we
942 can make the console window non-fixed-size, and continue
943 around the loop, hopefully, this will allow the layout to be
944 applied correctly.
945
946 If we still make it around the loop without moving used_size
947 closer to available_size, then there's nothing more we can do,
948 and we break out of the loop. */
949 if (idx == last_index)
950 {
951 /* If the used_size is greater than the available_size then
952 this indicates that the fixed-sized sub-layouts claimed
953 more space than is available. This layout is not going to
954 work. Our only hope at this point is to make the cmd
955 window non-fixed-size (if possible), and hope we can
956 shrink this enough to fit the rest of the sub-layouts in.
957
958 Alternatively, we've made it around the loop without
959 adjusting any window's size. This likely means all
960 windows have hit their min or max size. Again, our only
961 hope is to make the cmd window non-fixed-size, and hope
962 this fixes all our problems. */
963 if (old_cmd_info.has_value ()
964 && ((available_size < used_size)
965 || !found_window_that_can_grow_p))
966 {
967 info[old_cmd_info->index].min_size = old_cmd_info->min_size;
968 info[old_cmd_info->index].max_size = old_cmd_info->max_size;
969 tui_debug_printf
970 ("restoring index %d (cmd) size limits, min = %d, max = %d",
971 old_cmd_info->index, old_cmd_info->min_size,
972 old_cmd_info->max_size);
973 old_cmd_info.reset ();
974 }
975 else if (!found_window_that_can_grow_p)
976 break;
977 found_window_that_can_grow_p = false;
978 }
979
980 if (available_size > used_size
981 && info[idx].size < info[idx].max_size)
982 {
983 found_window_that_can_grow_p = true;
984 info[idx].size += 1;
985 used_size += 1;
986 }
987 else if (available_size < used_size
988 && info[idx].size > info[idx].min_size)
989 {
990 found_window_that_can_grow_p = true;
991 info[idx].size -= 1;
992 used_size -= 1;
993 }
994 }
995
996 if (debug_tui)
997 {
998 tui_debug_printf ("after final size calculation");
999 tui_debug_printf ("available_size = %d, used_size = %d",
1000 available_size, used_size);
1001 tui_debug_printf ("total_weight = %d, last_index = %d",
1002 total_weight, last_index);
1003 tui_debug_print_size_info (info);
1004 }
1005 }
1006
1007 /* Step 3: Resize. */
1008 int size_accum = 0;
1009 const int maximum = m_vertical ? height : width;
1010 for (int i = 0; i < m_splits.size (); ++i)
1011 {
1012 /* If we fall off the bottom, just make allocations overlap.
1013 GIGO. */
1014 if (size_accum + info[i].size > maximum)
1015 size_accum = maximum - info[i].size;
1016 else if (info[i].share_box)
1017 --size_accum;
1018 if (m_vertical)
1019 m_splits[i].layout->apply (x, y + size_accum, width, info[i].size,
1020 preserve_cmd_win_size_p);
1021 else
1022 m_splits[i].layout->apply (x + size_accum, y, info[i].size, height,
1023 preserve_cmd_win_size_p);
1024 size_accum += info[i].size;
1025 }
1026 }
1027
1028 /* See tui-layout.h. */
1029
1030 void
1031 tui_layout_split::remove_windows (const char *name)
1032 {
1033 for (int i = 0; i < m_splits.size (); ++i)
1034 {
1035 const char *this_name = m_splits[i].layout->get_name ();
1036 if (this_name == nullptr)
1037 m_splits[i].layout->remove_windows (name);
1038 else if (strcmp (this_name, name) == 0
1039 || strcmp (this_name, CMD_NAME) == 0
1040 || strcmp (this_name, STATUS_NAME) == 0)
1041 {
1042 /* Keep. */
1043 }
1044 else
1045 {
1046 m_splits.erase (m_splits.begin () + i);
1047 --i;
1048 }
1049 }
1050 }
1051
1052 /* See tui-layout.h. */
1053
1054 void
1055 tui_layout_split::replace_window (const char *name, const char *new_window)
1056 {
1057 for (auto &item : m_splits)
1058 item.layout->replace_window (name, new_window);
1059 }
1060
1061 /* See tui-layout.h. */
1062
1063 void
1064 tui_layout_split::specification (ui_file *output, int depth)
1065 {
1066 if (depth > 0)
1067 gdb_puts ("{", output);
1068
1069 if (!m_vertical)
1070 gdb_puts ("-horizontal ", output);
1071
1072 bool first = true;
1073 for (auto &item : m_splits)
1074 {
1075 if (!first)
1076 gdb_puts (" ", output);
1077 first = false;
1078 item.layout->specification (output, depth + 1);
1079 gdb_printf (output, " %d", item.weight);
1080 }
1081
1082 if (depth > 0)
1083 gdb_puts ("}", output);
1084 }
1085
1086 /* See tui-layout.h. */
1087
1088 std::string
1089 tui_layout_split::layout_fingerprint () const
1090 {
1091 for (auto &item : m_splits)
1092 {
1093 std::string fp = item.layout->layout_fingerprint ();
1094 if (!fp.empty ())
1095 return std::string (m_vertical ? "V" : "H") + fp;
1096 }
1097
1098 return "";
1099 }
1100
1101 /* Destroy the layout associated with SELF. */
1102
1103 static void
1104 destroy_layout (struct cmd_list_element *self, void *context)
1105 {
1106 tui_layout_split *layout = (tui_layout_split *) context;
1107 size_t index = find_layout (layout);
1108 layouts.erase (layouts.begin () + index);
1109 }
1110
1111 /* List holding the sub-commands of "layout". */
1112
1113 static struct cmd_list_element *layout_list;
1114
1115 /* Called to implement 'tui layout'. */
1116
1117 static void
1118 tui_layout_command (const char *args, int from_tty)
1119 {
1120 help_list (layout_list, "tui layout ", all_commands, gdb_stdout);
1121 }
1122
1123 /* Add a "layout" command with name NAME that switches to LAYOUT. */
1124
1125 static struct cmd_list_element *
1126 add_layout_command (const char *name, tui_layout_split *layout)
1127 {
1128 struct cmd_list_element *cmd;
1129
1130 string_file spec;
1131 layout->specification (&spec, 0);
1132
1133 gdb::unique_xmalloc_ptr<char> doc
1134 = xstrprintf (_("Apply the \"%s\" layout.\n\
1135 This layout was created using:\n\
1136 tui new-layout %s %s"),
1137 name, name, spec.c_str ());
1138
1139 cmd = add_cmd (name, class_tui, nullptr, doc.get (), &layout_list);
1140 cmd->set_context (layout);
1141 /* There is no API to set this. */
1142 cmd->func = tui_apply_layout;
1143 cmd->destroyer = destroy_layout;
1144 cmd->doc_allocated = 1;
1145 doc.release ();
1146 layouts.emplace_back (layout);
1147
1148 return cmd;
1149 }
1150
1151 /* Initialize the standard layouts. */
1152
1153 static void
1154 initialize_layouts ()
1155 {
1156 tui_layout_split *layout;
1157
1158 layout = new tui_layout_split ();
1159 layout->add_window (SRC_NAME, 2);
1160 layout->add_window (STATUS_NAME, 0);
1161 layout->add_window (CMD_NAME, 1);
1162 add_layout_command (SRC_NAME, layout);
1163
1164 layout = new tui_layout_split ();
1165 layout->add_window (DISASSEM_NAME, 2);
1166 layout->add_window (STATUS_NAME, 0);
1167 layout->add_window (CMD_NAME, 1);
1168 add_layout_command (DISASSEM_NAME, layout);
1169
1170 layout = new tui_layout_split ();
1171 layout->add_window (SRC_NAME, 1);
1172 layout->add_window (DISASSEM_NAME, 1);
1173 layout->add_window (STATUS_NAME, 0);
1174 layout->add_window (CMD_NAME, 1);
1175 add_layout_command ("split", layout);
1176
1177 layout = new tui_layout_split ();
1178 layout->add_window (DATA_NAME, 1);
1179 layout->add_window (SRC_NAME, 1);
1180 layout->add_window (STATUS_NAME, 0);
1181 layout->add_window (CMD_NAME, 1);
1182 layouts.emplace_back (layout);
1183 src_regs_layout = layout;
1184
1185 layout = new tui_layout_split ();
1186 layout->add_window (DATA_NAME, 1);
1187 layout->add_window (DISASSEM_NAME, 1);
1188 layout->add_window (STATUS_NAME, 0);
1189 layout->add_window (CMD_NAME, 1);
1190 layouts.emplace_back (layout);
1191 asm_regs_layout = layout;
1192 }
1193
1194 \f
1195
1196 /* A helper function that returns true if NAME is the name of an
1197 available window. */
1198
1199 static bool
1200 validate_window_name (const std::string &name)
1201 {
1202 auto iter = known_window_types->find (name);
1203 return iter != known_window_types->end ();
1204 }
1205
1206 /* Implementation of the "tui new-layout" command. */
1207
1208 static void
1209 tui_new_layout_command (const char *spec, int from_tty)
1210 {
1211 std::string new_name = extract_arg (&spec);
1212 if (new_name.empty ())
1213 error (_("No layout name specified"));
1214 if (new_name[0] == '-')
1215 error (_("Layout name cannot start with '-'"));
1216
1217 bool is_vertical = true;
1218 spec = skip_spaces (spec);
1219 if (check_for_argument (&spec, "-horizontal"))
1220 is_vertical = false;
1221
1222 std::vector<std::unique_ptr<tui_layout_split>> splits;
1223 splits.emplace_back (new tui_layout_split (is_vertical));
1224 std::unordered_set<std::string> seen_windows;
1225 while (true)
1226 {
1227 spec = skip_spaces (spec);
1228 if (spec[0] == '\0')
1229 break;
1230
1231 if (spec[0] == '{')
1232 {
1233 is_vertical = true;
1234 spec = skip_spaces (spec + 1);
1235 if (check_for_argument (&spec, "-horizontal"))
1236 is_vertical = false;
1237 splits.emplace_back (new tui_layout_split (is_vertical));
1238 continue;
1239 }
1240
1241 bool is_close = false;
1242 std::string name;
1243 if (spec[0] == '}')
1244 {
1245 is_close = true;
1246 ++spec;
1247 if (splits.size () == 1)
1248 error (_("Extra '}' in layout specification"));
1249 }
1250 else
1251 {
1252 name = extract_arg (&spec);
1253 if (name.empty ())
1254 break;
1255 if (!validate_window_name (name))
1256 error (_("Unknown window \"%s\""), name.c_str ());
1257 if (seen_windows.find (name) != seen_windows.end ())
1258 error (_("Window \"%s\" seen twice in layout"), name.c_str ());
1259 }
1260
1261 ULONGEST weight = get_ulongest (&spec, '}');
1262 if ((int) weight != weight)
1263 error (_("Weight out of range: %s"), pulongest (weight));
1264 if (is_close)
1265 {
1266 std::unique_ptr<tui_layout_split> last_split
1267 = std::move (splits.back ());
1268 splits.pop_back ();
1269 splits.back ()->add_split (std::move (last_split), weight);
1270 }
1271 else
1272 {
1273 splits.back ()->add_window (name.c_str (), weight);
1274 seen_windows.insert (name);
1275 }
1276 }
1277 if (splits.size () > 1)
1278 error (_("Missing '}' in layout specification"));
1279 if (seen_windows.empty ())
1280 error (_("New layout does not contain any windows"));
1281 if (seen_windows.find (CMD_NAME) == seen_windows.end ())
1282 error (_("New layout does not contain the \"" CMD_NAME "\" window"));
1283
1284 gdb::unique_xmalloc_ptr<char> cmd_name
1285 = make_unique_xstrdup (new_name.c_str ());
1286 std::unique_ptr<tui_layout_split> new_layout = std::move (splits.back ());
1287 struct cmd_list_element *cmd
1288 = add_layout_command (cmd_name.get (), new_layout.get ());
1289 cmd->name_allocated = 1;
1290 cmd_name.release ();
1291 new_layout.release ();
1292 }
1293
1294 /* Function to initialize gdb commands, for tui window layout
1295 manipulation. */
1296
1297 void _initialize_tui_layout ();
1298 void
1299 _initialize_tui_layout ()
1300 {
1301 struct cmd_list_element *layout_cmd
1302 = add_prefix_cmd ("layout", class_tui, tui_layout_command, _("\
1303 Change the layout of windows.\n\
1304 Usage: tui layout prev | next | LAYOUT-NAME"),
1305 &layout_list, 0, tui_get_cmd_list ());
1306 add_com_alias ("layout", layout_cmd, class_tui, 0);
1307
1308 add_cmd ("next", class_tui, tui_next_layout_command,
1309 _("Apply the next TUI layout."),
1310 &layout_list);
1311 add_cmd ("prev", class_tui, tui_prev_layout_command,
1312 _("Apply the previous TUI layout."),
1313 &layout_list);
1314 add_cmd ("regs", class_tui, tui_regs_layout_command,
1315 _("Apply the TUI register layout."),
1316 &layout_list);
1317
1318 add_cmd ("new-layout", class_tui, tui_new_layout_command,
1319 _("Create a new TUI layout.\n\
1320 Usage: tui new-layout [-horizontal] NAME WINDOW WEIGHT [WINDOW WEIGHT]...\n\
1321 Create a new TUI layout. The new layout will be named NAME,\n\
1322 and can be accessed using \"layout NAME\".\n\
1323 The windows will be displayed in the specified order.\n\
1324 A WINDOW can also be of the form:\n\
1325 { [-horizontal] NAME WEIGHT [NAME WEIGHT]... }\n\
1326 This form indicates a sub-frame.\n\
1327 Each WEIGHT is an integer, which holds the relative size\n\
1328 to be allocated to the window."),
1329 tui_get_cmd_list ());
1330
1331 initialize_layouts ();
1332 initialize_known_windows ();
1333 }