tui-layout.c revision 1.10 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
334
336 /* Helper function to create one of the built-in (non-locator)
337 windows. */
338
339 template<enum tui_win_type V, class T>
340 static tui_win_info *
341 make_standard_window (const char *)
342 {
343 if (tui_win_list[V] == nullptr)
344 tui_win_list[V] = new T ();
345 return tui_win_list[V];
346 }
347
348 /* A map holding all the known window types, keyed by name. Note that
349 this is heap-allocated and "leaked" at gdb exit. This avoids
350 ordering issues with destroying elements in the map at shutdown.
351 In particular, destroying this map can occur after Python has been
352 shut down, causing crashes if any window destruction requires
353 running Python code. */
354
355 static std::unordered_map<std::string, window_factory> *known_window_types;
356
357 /* Helper function that returns a TUI window, given its name. */
358
359 static tui_win_info *
360 tui_get_window_by_name (const std::string &name)
361 {
362 for (tui_win_info *window : tui_windows)
363 if (name == window->name ())
364 return window;
365
366 auto iter = known_window_types->find (name);
367 if (iter == known_window_types->end ())
368 error (_("Unknown window type \"%s\""), name.c_str ());
369
370 tui_win_info *result = iter->second (name.c_str ());
371 if (result == nullptr)
372 error (_("Could not create window \"%s\""), name.c_str ());
373 return result;
374 }
375
376 /* Initialize the known window types. */
377
378 static void
379 initialize_known_windows ()
380 {
381 known_window_types = new std::unordered_map<std::string, window_factory>;
382
383 known_window_types->emplace (SRC_NAME,
384 make_standard_window<SRC_WIN,
385 tui_source_window>);
386 known_window_types->emplace (CMD_NAME,
387 make_standard_window<CMD_WIN, tui_cmd_window>);
388 known_window_types->emplace (DATA_NAME,
389 make_standard_window<DATA_WIN,
390 tui_data_window>);
391 known_window_types->emplace (DISASSEM_NAME,
392 make_standard_window<DISASSEM_WIN,
393 tui_disasm_window>);
394 known_window_types->emplace (STATUS_NAME,
395 make_standard_window<STATUS_WIN,
396 tui_locator_window>);
397 }
398
399 /* See tui-layout.h. */
400
401 void
402 tui_register_window (const char *name, window_factory &&factory)
403 {
404 std::string name_copy = name;
405
406 if (name_copy == SRC_NAME || name_copy == CMD_NAME || name_copy == DATA_NAME
407 || name_copy == DISASSEM_NAME || name_copy == STATUS_NAME)
408 error (_("Window type \"%s\" is built-in"), name);
409
410 for (const char &c : name_copy)
411 {
412 if (ISSPACE (c))
413 error (_("invalid whitespace character in window name"));
414
415 if (!ISALNUM (c) && strchr ("-_.", c) == nullptr)
416 error (_("invalid character '%c' in window name"), c);
417 }
418
419 if (!ISALPHA (name_copy[0]))
420 error (_("window name must start with a letter, not '%c'"), name_copy[0]);
421
422 known_window_types->emplace (std::move (name_copy),
423 std::move (factory));
424 }
425
426 /* See tui-layout.h. */
427
428 std::unique_ptr<tui_layout_base>
429 tui_layout_window::clone () const
430 {
431 tui_layout_window *result = new tui_layout_window (m_contents.c_str ());
432 return std::unique_ptr<tui_layout_base> (result);
433 }
434
435 /* See tui-layout.h. */
436
437 void
438 tui_layout_window::apply (int x_, int y_, int width_, int height_,
439 bool preserve_cmd_win_size_p)
440 {
441 x = x_;
442 y = y_;
443 width = width_;
444 height = height_;
445 gdb_assert (m_window != nullptr);
446 m_window->resize (height, width, x, y);
447 }
448
449 /* See tui-layout.h. */
450
451 void
452 tui_layout_window::get_sizes (bool height, int *min_value, int *max_value)
453 {
454 TUI_SCOPED_DEBUG_ENTER_EXIT;
455
456 if (m_window == nullptr)
457 m_window = tui_get_window_by_name (m_contents);
458
459 tui_debug_printf ("window = %s, getting %s",
460 m_window->name (), (height ? "height" : "width"));
461
462 if (height)
463 {
464 *min_value = m_window->min_height ();
465 *max_value = m_window->max_height ();
466 }
467 else
468 {
469 *min_value = m_window->min_width ();
470 *max_value = m_window->max_width ();
471 }
472
473 tui_debug_printf ("min = %d, max = %d", *min_value, *max_value);
474 }
475
476 /* See tui-layout.h. */
477
478 bool
479 tui_layout_window::first_edge_has_border_p () const
480 {
481 gdb_assert (m_window != nullptr);
482 return m_window->can_box ();
483 }
484
485 /* See tui-layout.h. */
486
487 bool
488 tui_layout_window::last_edge_has_border_p () const
489 {
490 gdb_assert (m_window != nullptr);
491 return m_window->can_box ();
492 }
493
494 /* See tui-layout.h. */
495
496 void
497 tui_layout_window::replace_window (const char *name, const char *new_window)
498 {
499 if (m_contents == name)
500 {
501 m_contents = new_window;
502 if (m_window != nullptr)
503 {
504 m_window->make_visible (false);
505 m_window = tui_get_window_by_name (m_contents);
506 }
507 }
508 }
509
510 /* See tui-layout.h. */
511
512 void
513 tui_layout_window::specification (ui_file *output, int depth)
514 {
515 gdb_puts (get_name (), output);
516 }
517
518 /* See tui-layout.h. */
519
520 std::string
521 tui_layout_window::layout_fingerprint () const
522 {
523 if (strcmp (get_name (), "cmd") == 0)
524 return "C";
525 else
526 return "";
527 }
528
529 /* See tui-layout.h. */
530
531 void
532 tui_layout_split::add_split (std::unique_ptr<tui_layout_split> &&layout,
533 int weight)
534 {
535 split s = {weight, std::move (layout)};
536 m_splits.push_back (std::move (s));
537 }
538
539 /* See tui-layout.h. */
540
541 void
542 tui_layout_split::add_window (const char *name, int weight)
543 {
544 tui_layout_window *result = new tui_layout_window (name);
545 split s = {weight, std::unique_ptr<tui_layout_base> (result)};
546 m_splits.push_back (std::move (s));
547 }
548
549 /* See tui-layout.h. */
550
551 std::unique_ptr<tui_layout_base>
552 tui_layout_split::clone () const
553 {
554 tui_layout_split *result = new tui_layout_split (m_vertical);
555 for (const split &item : m_splits)
556 {
557 std::unique_ptr<tui_layout_base> next = item.layout->clone ();
558 split s = {item.weight, std::move (next)};
559 result->m_splits.push_back (std::move (s));
560 }
561 return std::unique_ptr<tui_layout_base> (result);
562 }
563
564 /* See tui-layout.h. */
565
566 void
567 tui_layout_split::get_sizes (bool height, int *min_value, int *max_value)
568 {
569 TUI_SCOPED_DEBUG_ENTER_EXIT;
570
571 *min_value = 0;
572 *max_value = 0;
573 bool first_time = true;
574 for (const split &item : m_splits)
575 {
576 int new_min, new_max;
577 item.layout->get_sizes (height, &new_min, &new_max);
578 /* For the mismatch case, the first time through we want to set
579 the min and max to the computed values -- the "first_time"
580 check here is just a funny way of doing that. */
581 if (height == m_vertical || first_time)
582 {
583 *min_value += new_min;
584 *max_value += new_max;
585 }
586 else
587 {
588 *min_value = std::max (*min_value, new_min);
589 *max_value = std::min (*max_value, new_max);
590 }
591 first_time = false;
592 }
593
594 tui_debug_printf ("min_value = %d, max_value = %d", *min_value, *max_value);
595 }
596
597 /* See tui-layout.h. */
598
599 bool
600 tui_layout_split::first_edge_has_border_p () const
601 {
602 if (m_splits.empty ())
603 return false;
604 return m_splits[0].layout->first_edge_has_border_p ();
605 }
606
607 /* See tui-layout.h. */
608
609 bool
610 tui_layout_split::last_edge_has_border_p () const
611 {
612 if (m_splits.empty ())
613 return false;
614 return m_splits.back ().layout->last_edge_has_border_p ();
615 }
616
617 /* See tui-layout.h. */
618
619 void
620 tui_layout_split::set_weights_from_sizes ()
621 {
622 for (int i = 0; i < m_splits.size (); ++i)
623 m_splits[i].weight
624 = m_vertical ? m_splits[i].layout->height : m_splits[i].layout->width;
625 }
626
627 /* See tui-layout.h. */
628
629 std::string
630 tui_layout_split::tui_debug_weights_to_string () const
631 {
632 std::string str;
633
634 for (int i = 0; i < m_splits.size (); ++i)
635 {
636 if (i > 0)
637 str += ", ";
638 str += string_printf ("[%d] %d", i, m_splits[i].weight);
639 }
640
641 return str;
642 }
643
644 /* See tui-layout.h. */
645
646 void
647 tui_layout_split::tui_debug_print_size_info
648 (const std::vector<tui_layout_split::size_info> &info)
649 {
650 gdb_assert (debug_tui);
651
652 tui_debug_printf ("current size info data:");
653 for (int i = 0; i < info.size (); ++i)
654 tui_debug_printf (" [%d] { size = %d, min = %d, max = %d, share_box = %d }",
655 i, info[i].size, info[i].min_size,
656 info[i].max_size, info[i].share_box);
657 }
658
659 /* See tui-layout.h. */
660
661 tui_adjust_result
662 tui_layout_split::set_size (const char *name, int new_size, bool set_width_p)
663 {
664 TUI_SCOPED_DEBUG_ENTER_EXIT;
665
666 tui_debug_printf ("this = %p, name = %s, new_size = %d",
667 this, name, new_size);
668
669 /* Look through the children. If one is a layout holding the named
670 window, we're done; or if one actually is the named window,
671 update it. */
672 int found_index = -1;
673 for (int i = 0; i < m_splits.size (); ++i)
674 {
675 tui_adjust_result adjusted;
676 if (set_width_p)
677 adjusted = m_splits[i].layout->set_width (name, new_size);
678 else
679 adjusted = m_splits[i].layout->set_height (name, new_size);
680 if (adjusted == HANDLED)
681 return HANDLED;
682 if (adjusted == FOUND)
683 {
684 if (set_width_p ? m_vertical : !m_vertical)
685 return FOUND;
686 found_index = i;
687 break;
688 }
689 }
690
691 if (found_index == -1)
692 return NOT_FOUND;
693 int curr_size = (set_width_p
694 ? m_splits[found_index].layout->width
695 : m_splits[found_index].layout->height);
696 if (curr_size == new_size)
697 return HANDLED;
698
699 tui_debug_printf ("found window %s at index %d", name, found_index);
700
701 set_weights_from_sizes ();
702 int delta = m_splits[found_index].weight - new_size;
703 m_splits[found_index].weight = new_size;
704
705 tui_debug_printf ("before delta (%d) distribution, weights: %s",
706 delta, tui_debug_weights_to_string ().c_str ());
707
708 /* Distribute the "delta" over all other windows, while respecting their
709 min/max sizes. We grow each window by 1 line at a time continually
710 looping over all the windows. However, skip the window that the user
711 just resized, obviously we don't want to readjust that window. */
712 bool found_window_that_can_grow_p = true;
713 for (int i = 0; delta != 0; i = (i + 1) % m_splits.size ())
714 {
715 int index = (found_index + 1 + i) % m_splits.size ();
716 if (index == found_index)
717 {
718 if (!found_window_that_can_grow_p)
719 break;
720 found_window_that_can_grow_p = false;
721 continue;
722 }
723
724 int new_min, new_max;
725 m_splits[index].layout->get_sizes (m_vertical, &new_min, &new_max);
726
727 if (delta < 0)
728 {
729 /* The primary window grew, so we are trying to shrink other
730 windows. */
731 if (m_splits[index].weight > new_min)
732 {
733 m_splits[index].weight -= 1;
734 delta += 1;
735 found_window_that_can_grow_p = true;
736 }
737 }
738 else
739 {
740 /* The primary window shrank, so we are trying to grow other
741 windows. */
742 if (m_splits[index].weight < new_max)
743 {
744 m_splits[index].weight += 1;
745 delta -= 1;
746 found_window_that_can_grow_p = true;
747 }
748 }
749
750 tui_debug_printf ("index = %d, weight now: %d",
751 index, m_splits[index].weight);
752 }
753
754 tui_debug_printf ("after delta (%d) distribution, weights: %s",
755 delta, tui_debug_weights_to_string ().c_str ());
756
757 if (delta != 0)
758 {
759 if (set_width_p)
760 warning (_("Invalid window width specified"));
761 else
762 warning (_("Invalid window height specified"));
763 /* Effectively undo any modifications made here. */
764 set_weights_from_sizes ();
765 }
766 else
767 {
768 /* Simply re-apply the updated layout. We pass false here so that
769 the cmd window can be resized. However, we should have already
770 resized everything above to be "just right", so the apply call
771 here should not end up changing the sizes at all. */
772 apply (x, y, width, height, false);
773 }
774
775 return HANDLED;
776 }
777
778 /* See tui-layout.h. */
779
780 void
781 tui_layout_split::apply (int x_, int y_, int width_, int height_,
782 bool preserve_cmd_win_size_p)
783 {
784 TUI_SCOPED_DEBUG_ENTER_EXIT;
785
786 x = x_;
787 y = y_;
788 width = width_;
789 height = height_;
790
791 /* In some situations we fix the size of the cmd window. However,
792 occasionally this turns out to be a mistake. This struct is used to
793 hold the original information about the cmd window, so we can restore
794 it if needed. */
795 struct old_size_info
796 {
797 /* Constructor. */
798 old_size_info (int index_, int min_size_, int max_size_)
799 : index (index_),
800 min_size (min_size_),
801 max_size (max_size_)
802 { /* Nothing. */ }
803
804 /* The index in m_splits where the cmd window was found. */
805 int index;
806
807 /* The previous min/max size. */
808 int min_size;
809 int max_size;
810 };
811
812 /* This is given a value only if we fix the size of the cmd window. */
813 gdb::optional<old_size_info> old_cmd_info;
814
815 std::vector<size_info> info (m_splits.size ());
816
817 tui_debug_printf ("weights are: %s",
818 tui_debug_weights_to_string ().c_str ());
819
820 /* Step 1: Find the min and max size of each sub-layout.
821 Fixed-sized layouts are given their desired size, and then the
822 remaining space is distributed among the remaining windows
823 according to the weights given. */
824 int available_size = m_vertical ? height : width;
825 int last_index = -1;
826 int total_weight = 0;
827 for (int i = 0; i < m_splits.size (); ++i)
828 {
829 bool cmd_win_already_exists = TUI_CMD_WIN != nullptr;
830
831 /* Always call get_sizes, to ensure that the window is
832 instantiated. This is a bit gross but less gross than adding
833 special cases for this in other places. */
834 m_splits[i].layout->get_sizes (m_vertical, &info[i].min_size,
835 &info[i].max_size);
836
837 if (preserve_cmd_win_size_p
838 && cmd_win_already_exists
839 && m_splits[i].layout->get_name () != nullptr
840 && strcmp (m_splits[i].layout->get_name (), "cmd") == 0)
841 {
842 /* Save the old cmd window information, in case we need to
843 restore it later. */
844 old_cmd_info.emplace (i, info[i].min_size, info[i].max_size);
845
846 /* If this layout has never been applied, then it means the
847 user just changed the layout. In this situation, it's
848 desirable to keep the size of the command window the
849 same. Setting the min and max sizes this way ensures
850 that the resizing step, below, does the right thing with
851 this window. */
852 info[i].min_size = (m_vertical
853 ? TUI_CMD_WIN->height
854 : TUI_CMD_WIN->width);
855 info[i].max_size = info[i].min_size;
856 }
857
858 if (info[i].min_size == info[i].max_size)
859 available_size -= info[i].min_size;
860 else
861 {
862 last_index = i;
863 total_weight += m_splits[i].weight;
864 }
865
866 /* Two adjacent boxed windows will share a border, making a bit
867 more size available. */
868 if (i > 0
869 && m_splits[i - 1].layout->last_edge_has_border_p ()
870 && m_splits[i].layout->first_edge_has_border_p ())
871 info[i].share_box = true;
872 }
873
874 /* If last_index is set then we have a window that is not of a fixed
875 size. This window will have its size calculated below, which requires
876 that the total_weight not be zero (we divide by total_weight, so don't
877 want a floating-point exception). */
878 gdb_assert (last_index == -1 || total_weight > 0);
879
880 /* Step 2: Compute the size of each sub-layout. Fixed-sized items
881 are given their fixed size, while others are resized according to
882 their weight. */
883 int used_size = 0;
884 for (int i = 0; i < m_splits.size (); ++i)
885 {
886 if (info[i].min_size != info[i].max_size)
887 {
888 /* Compute the height and clamp to the allowable range. */
889 info[i].size = available_size * m_splits[i].weight / total_weight;
890 if (info[i].size > info[i].max_size)
891 info[i].size = info[i].max_size;
892 if (info[i].size < info[i].min_size)
893 info[i].size = info[i].min_size;
894 /* Keep a total of all the size we've used so far (we gain some
895 size back if this window can share a border with a preceding
896 window). Any unused space will be distributed between all of
897 the other windows (while respecting min/max sizes) later in
898 this function. */
899 used_size += info[i].size;
900 if (info[i].share_box)
901 --used_size;
902 }
903 else
904 info[i].size = info[i].min_size;
905 }
906
907 if (debug_tui)
908 {
909 tui_debug_printf ("after initial size calculation");
910 tui_debug_printf ("available_size = %d, used_size = %d",
911 available_size, used_size);
912 tui_debug_printf ("total_weight = %d, last_index = %d",
913 total_weight, last_index);
914 tui_debug_print_size_info (info);
915 }
916
917 /* If we didn't find any sub-layouts that were of a non-fixed size, but
918 we did find the cmd window, then we can consider that a sort-of
919 non-fixed size sub-layout.
920
921 The cmd window might, initially, be of a fixed size (see above), but,
922 we are willing to relax this constraint if required to correctly apply
923 this layout (see below). */
924 if (last_index == -1 && old_cmd_info.has_value ())
925 last_index = old_cmd_info->index;
926
927 /* Allocate any leftover size. */
928 if (available_size != used_size && last_index != -1)
929 {
930 /* Loop over all windows until the amount of used space is equal to
931 the amount of available space. There's an escape hatch within
932 the loop in case we can't find any sub-layouts to resize. */
933 bool found_window_that_can_grow_p = true;
934 for (int idx = last_index;
935 available_size != used_size;
936 idx = (idx + 1) % m_splits.size ())
937 {
938 /* Every time we get back to last_index, which is where the loop
939 started, we check to make sure that we did assign some space
940 to a window, bringing used_size closer to available_size.
941
942 If we didn't, but the cmd window is of a fixed size, then we
943 can make the console window non-fixed-size, and continue
944 around the loop, hopefully, this will allow the layout to be
945 applied correctly.
946
947 If we still make it around the loop without moving used_size
948 closer to available_size, then there's nothing more we can do,
949 and we break out of the loop. */
950 if (idx == last_index)
951 {
952 /* If the used_size is greater than the available_size then
953 this indicates that the fixed-sized sub-layouts claimed
954 more space than is available. This layout is not going to
955 work. Our only hope at this point is to make the cmd
956 window non-fixed-size (if possible), and hope we can
957 shrink this enough to fit the rest of the sub-layouts in.
958
959 Alternatively, we've made it around the loop without
960 adjusting any window's size. This likely means all
961 windows have hit their min or max size. Again, our only
962 hope is to make the cmd window non-fixed-size, and hope
963 this fixes all our problems. */
964 if (old_cmd_info.has_value ()
965 && ((available_size < used_size)
966 || !found_window_that_can_grow_p))
967 {
968 info[old_cmd_info->index].min_size = old_cmd_info->min_size;
969 info[old_cmd_info->index].max_size = old_cmd_info->max_size;
970 tui_debug_printf
971 ("restoring index %d (cmd) size limits, min = %d, max = %d",
972 old_cmd_info->index, old_cmd_info->min_size,
973 old_cmd_info->max_size);
974 old_cmd_info.reset ();
975 }
976 else if (!found_window_that_can_grow_p)
977 break;
978 found_window_that_can_grow_p = false;
979 }
980
981 if (available_size > used_size
982 && info[idx].size < info[idx].max_size)
983 {
984 found_window_that_can_grow_p = true;
985 info[idx].size += 1;
986 used_size += 1;
987 }
988 else if (available_size < used_size
989 && info[idx].size > info[idx].min_size)
990 {
991 found_window_that_can_grow_p = true;
992 info[idx].size -= 1;
993 used_size -= 1;
994 }
995 }
996
997 if (debug_tui)
998 {
999 tui_debug_printf ("after final size calculation");
1000 tui_debug_printf ("available_size = %d, used_size = %d",
1001 available_size, used_size);
1002 tui_debug_printf ("total_weight = %d, last_index = %d",
1003 total_weight, last_index);
1004 tui_debug_print_size_info (info);
1005 }
1006 }
1007
1008 /* Step 3: Resize. */
1009 int size_accum = 0;
1010 const int maximum = m_vertical ? height : width;
1011 for (int i = 0; i < m_splits.size (); ++i)
1012 {
1013 /* If we fall off the bottom, just make allocations overlap.
1014 GIGO. */
1015 if (size_accum + info[i].size > maximum)
1016 size_accum = maximum - info[i].size;
1017 else if (info[i].share_box)
1018 --size_accum;
1019 if (m_vertical)
1020 m_splits[i].layout->apply (x, y + size_accum, width, info[i].size,
1021 preserve_cmd_win_size_p);
1022 else
1023 m_splits[i].layout->apply (x + size_accum, y, info[i].size, height,
1024 preserve_cmd_win_size_p);
1025 size_accum += info[i].size;
1026 }
1027 }
1028
1029 /* See tui-layout.h. */
1030
1031 void
1032 tui_layout_split::remove_windows (const char *name)
1033 {
1034 for (int i = 0; i < m_splits.size (); ++i)
1035 {
1036 const char *this_name = m_splits[i].layout->get_name ();
1037 if (this_name == nullptr)
1038 m_splits[i].layout->remove_windows (name);
1039 else if (strcmp (this_name, name) == 0
1040 || strcmp (this_name, CMD_NAME) == 0
1041 || strcmp (this_name, STATUS_NAME) == 0)
1042 {
1043 /* Keep. */
1044 }
1045 else
1046 {
1047 m_splits.erase (m_splits.begin () + i);
1048 --i;
1049 }
1050 }
1051 }
1052
1053 /* See tui-layout.h. */
1054
1055 void
1056 tui_layout_split::replace_window (const char *name, const char *new_window)
1057 {
1058 for (auto &item : m_splits)
1059 item.layout->replace_window (name, new_window);
1060 }
1061
1062 /* See tui-layout.h. */
1063
1064 void
1065 tui_layout_split::specification (ui_file *output, int depth)
1066 {
1067 if (depth > 0)
1068 gdb_puts ("{", output);
1069
1070 if (!m_vertical)
1071 gdb_puts ("-horizontal ", output);
1072
1073 bool first = true;
1074 for (auto &item : m_splits)
1075 {
1076 if (!first)
1077 gdb_puts (" ", output);
1078 first = false;
1079 item.layout->specification (output, depth + 1);
1080 gdb_printf (output, " %d", item.weight);
1081 }
1082
1083 if (depth > 0)
1084 gdb_puts ("}", output);
1085 }
1086
1087 /* See tui-layout.h. */
1088
1089 std::string
1090 tui_layout_split::layout_fingerprint () const
1091 {
1092 for (auto &item : m_splits)
1093 {
1094 std::string fp = item.layout->layout_fingerprint ();
1095 if (!fp.empty ())
1096 return std::string (m_vertical ? "V" : "H") + fp;
1097 }
1098
1099 return "";
1100 }
1101
1102 /* Destroy the layout associated with SELF. */
1103
1104 static void
1105 destroy_layout (struct cmd_list_element *self, void *context)
1106 {
1107 tui_layout_split *layout = (tui_layout_split *) context;
1108 size_t index = find_layout (layout);
1109 layouts.erase (layouts.begin () + index);
1110 }
1111
1112 /* List holding the sub-commands of "layout". */
1113
1114 static struct cmd_list_element *layout_list;
1115
1116 /* Called to implement 'tui layout'. */
1117
1118 static void
1119 tui_layout_command (const char *args, int from_tty)
1120 {
1121 help_list (layout_list, "tui layout ", all_commands, gdb_stdout);
1122 }
1123
1124 /* Add a "layout" command with name NAME that switches to LAYOUT. */
1125
1126 static struct cmd_list_element *
1127 add_layout_command (const char *name, tui_layout_split *layout)
1128 {
1129 struct cmd_list_element *cmd;
1130
1131 string_file spec;
1132 layout->specification (&spec, 0);
1133
1134 gdb::unique_xmalloc_ptr<char> doc
1135 = xstrprintf (_("Apply the \"%s\" layout.\n\
1136 This layout was created using:\n\
1137 tui new-layout %s %s"),
1138 name, name, spec.c_str ());
1139
1140 cmd = add_cmd (name, class_tui, nullptr, doc.get (), &layout_list);
1141 cmd->set_context (layout);
1142 /* There is no API to set this. */
1143 cmd->func = tui_apply_layout;
1144 cmd->destroyer = destroy_layout;
1145 cmd->doc_allocated = 1;
1146 doc.release ();
1147 layouts.emplace_back (layout);
1148
1149 return cmd;
1150 }
1151
1152 /* Initialize the standard layouts. */
1153
1154 static void
1155 initialize_layouts ()
1156 {
1157 tui_layout_split *layout;
1158
1159 layout = new tui_layout_split ();
1160 layout->add_window (SRC_NAME, 2);
1161 layout->add_window (STATUS_NAME, 0);
1162 layout->add_window (CMD_NAME, 1);
1163 add_layout_command (SRC_NAME, layout);
1164
1165 layout = new tui_layout_split ();
1166 layout->add_window (DISASSEM_NAME, 2);
1167 layout->add_window (STATUS_NAME, 0);
1168 layout->add_window (CMD_NAME, 1);
1169 add_layout_command (DISASSEM_NAME, layout);
1170
1171 layout = new tui_layout_split ();
1172 layout->add_window (SRC_NAME, 1);
1173 layout->add_window (DISASSEM_NAME, 1);
1174 layout->add_window (STATUS_NAME, 0);
1175 layout->add_window (CMD_NAME, 1);
1176 add_layout_command ("split", layout);
1177
1178 layout = new tui_layout_split ();
1179 layout->add_window (DATA_NAME, 1);
1180 layout->add_window (SRC_NAME, 1);
1181 layout->add_window (STATUS_NAME, 0);
1182 layout->add_window (CMD_NAME, 1);
1183 layouts.emplace_back (layout);
1184 src_regs_layout = layout;
1185
1186 layout = new tui_layout_split ();
1187 layout->add_window (DATA_NAME, 1);
1188 layout->add_window (DISASSEM_NAME, 1);
1189 layout->add_window (STATUS_NAME, 0);
1190 layout->add_window (CMD_NAME, 1);
1191 layouts.emplace_back (layout);
1192 asm_regs_layout = layout;
1193 }
1194
1195
1196
1198 /* A helper function that returns true if NAME is the name of an
1199 available window. */
1200
1201 static bool
1202 validate_window_name (const std::string &name)
1203 {
1204 auto iter = known_window_types->find (name);
1205 return iter != known_window_types->end ();
1206 }
1207
1208 /* Implementation of the "tui new-layout" command. */
1209
1210 static void
1211 tui_new_layout_command (const char *spec, int from_tty)
1212 {
1213 std::string new_name = extract_arg (&spec);
1214 if (new_name.empty ())
1215 error (_("No layout name specified"));
1216 if (new_name[0] == '-')
1217 error (_("Layout name cannot start with '-'"));
1218
1219 bool is_vertical = true;
1220 spec = skip_spaces (spec);
1221 if (check_for_argument (&spec, "-horizontal"))
1222 is_vertical = false;
1223
1224 std::vector<std::unique_ptr<tui_layout_split>> splits;
1225 splits.emplace_back (new tui_layout_split (is_vertical));
1226 std::unordered_set<std::string> seen_windows;
1227 while (true)
1228 {
1229 spec = skip_spaces (spec);
1230 if (spec[0] == '\0')
1231 break;
1232
1233 if (spec[0] == '{')
1234 {
1235 is_vertical = true;
1236 spec = skip_spaces (spec + 1);
1237 if (check_for_argument (&spec, "-horizontal"))
1238 is_vertical = false;
1239 splits.emplace_back (new tui_layout_split (is_vertical));
1240 continue;
1241 }
1242
1243 bool is_close = false;
1244 std::string name;
1245 if (spec[0] == '}')
1246 {
1247 is_close = true;
1248 ++spec;
1249 if (splits.size () == 1)
1250 error (_("Extra '}' in layout specification"));
1251 }
1252 else
1253 {
1254 name = extract_arg (&spec);
1255 if (name.empty ())
1256 break;
1257 if (!validate_window_name (name))
1258 error (_("Unknown window \"%s\""), name.c_str ());
1259 if (seen_windows.find (name) != seen_windows.end ())
1260 error (_("Window \"%s\" seen twice in layout"), name.c_str ());
1261 }
1262
1263 ULONGEST weight = get_ulongest (&spec, '}');
1264 if ((int) weight != weight)
1265 error (_("Weight out of range: %s"), pulongest (weight));
1266 if (is_close)
1267 {
1268 std::unique_ptr<tui_layout_split> last_split
1269 = std::move (splits.back ());
1270 splits.pop_back ();
1271 splits.back ()->add_split (std::move (last_split), weight);
1272 }
1273 else
1274 {
1275 splits.back ()->add_window (name.c_str (), weight);
1276 seen_windows.insert (name);
1277 }
1278 }
1279 if (splits.size () > 1)
1280 error (_("Missing '}' in layout specification"));
1281 if (seen_windows.empty ())
1282 error (_("New layout does not contain any windows"));
1283 if (seen_windows.find (CMD_NAME) == seen_windows.end ())
1284 error (_("New layout does not contain the \"" CMD_NAME "\" window"));
1285
1286 gdb::unique_xmalloc_ptr<char> cmd_name
1287 = make_unique_xstrdup (new_name.c_str ());
1288 std::unique_ptr<tui_layout_split> new_layout = std::move (splits.back ());
1289 struct cmd_list_element *cmd
1290 = add_layout_command (cmd_name.get (), new_layout.get ());
1291 cmd->name_allocated = 1;
1292 cmd_name.release ();
1293 new_layout.release ();
1294 }
1295
1296 /* Function to initialize gdb commands, for tui window layout
1297 manipulation. */
1298
1299 void _initialize_tui_layout ();
1300 void
1301 _initialize_tui_layout ()
1302 {
1303 struct cmd_list_element *layout_cmd
1304 = add_prefix_cmd ("layout", class_tui, tui_layout_command, _("\
1305 Change the layout of windows.\n\
1306 Usage: tui layout prev | next | LAYOUT-NAME"),
1307 &layout_list, 0, tui_get_cmd_list ());
1308 add_com_alias ("layout", layout_cmd, class_tui, 0);
1309
1310 add_cmd ("next", class_tui, tui_next_layout_command,
1311 _("Apply the next TUI layout."),
1312 &layout_list);
1313 add_cmd ("prev", class_tui, tui_prev_layout_command,
1314 _("Apply the previous TUI layout."),
1315 &layout_list);
1316 add_cmd ("regs", class_tui, tui_regs_layout_command,
1317 _("Apply the TUI register layout."),
1318 &layout_list);
1319
1320 add_cmd ("new-layout", class_tui, tui_new_layout_command,
1321 _("Create a new TUI layout.\n\
1322 Usage: tui new-layout [-horizontal] NAME WINDOW WEIGHT [WINDOW WEIGHT]...\n\
1323 Create a new TUI layout. The new layout will be named NAME,\n\
1324 and can be accessed using \"layout NAME\".\n\
1325 The windows will be displayed in the specified order.\n\
1326 A WINDOW can also be of the form:\n\
1327 { [-horizontal] NAME WEIGHT [NAME WEIGHT]... }\n\
1328 This form indicates a sub-frame.\n\
1329 Each WEIGHT is an integer, which holds the relative size\n\
1330 to be allocated to the window."),
1331 tui_get_cmd_list ());
1332
1333 initialize_layouts ();
1334 initialize_known_windows ();
1335 }
1336