Remove hard-coded TUI layouts
[deliverable/binutils-gdb.git] / gdb / tui / tui-layout.c
1 /* TUI layout window management.
2
3 Copyright (C) 1998-2020 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 <ctype.h>
31
32 #include "tui/tui.h"
33 #include "tui/tui-command.h"
34 #include "tui/tui-data.h"
35 #include "tui/tui-wingeneral.h"
36 #include "tui/tui-stack.h"
37 #include "tui/tui-regs.h"
38 #include "tui/tui-win.h"
39 #include "tui/tui-winsource.h"
40 #include "tui/tui-disasm.h"
41 #include "tui/tui-layout.h"
42 #include "tui/tui-source.h"
43 #include "gdb_curses.h"
44
45 static void tui_layout_command (const char *, int);
46 static void extract_display_start_addr (struct gdbarch **, CORE_ADDR *);
47
48 /* The layouts. */
49 static std::vector<std::unique_ptr<tui_layout_split>> layouts;
50
51 /* The layout that is currently applied. */
52 static std::unique_ptr<tui_layout_base> applied_layout;
53
54 /* The "skeleton" version of the layout that is currently applied. */
55 static tui_layout_split *applied_skeleton;
56
57 /* The two special "regs" layouts. Note that these aren't registered
58 as commands and so can never be deleted. */
59 static tui_layout_split *src_regs_layout;
60 static tui_layout_split *asm_regs_layout;
61
62 /* See tui-layout.h. */
63
64 void
65 tui_apply_current_layout ()
66 {
67 applied_layout->apply (0, 0, tui_term_width (), tui_term_height ());
68 }
69
70 /* See tui-layout. */
71
72 void
73 tui_adjust_window_height (struct tui_win_info *win, int new_height)
74 {
75 applied_layout->adjust_size (win->name (), new_height);
76 }
77
78 /* Set the current layout to LAYOUT. */
79
80 static void
81 tui_set_layout (tui_layout_split *layout)
82 {
83 struct gdbarch *gdbarch;
84 CORE_ADDR addr;
85
86 extract_display_start_addr (&gdbarch, &addr);
87 tui_make_all_invisible ();
88 applied_skeleton = layout;
89 applied_layout = layout->clone ();
90 tui_apply_current_layout ();
91 tui_delete_invisible_windows ();
92
93 if (gdbarch == nullptr && TUI_DISASM_WIN != nullptr)
94 tui_get_begin_asm_address (&gdbarch, &addr);
95 tui_update_source_windows_with_addr (gdbarch, addr);
96 }
97
98 /* See tui-layout.h. */
99
100 void
101 tui_add_win_to_layout (enum tui_win_type type)
102 {
103 gdb_assert (type == SRC_WIN || type == DISASSEM_WIN);
104
105 /* If the window already exists, no need to add it. */
106 if (tui_win_list[type] != nullptr)
107 return;
108
109 /* If the window we are trying to replace doesn't exist, we're
110 done. */
111 enum tui_win_type other = type == SRC_WIN ? DISASSEM_WIN : SRC_WIN;
112 if (tui_win_list[other] == nullptr)
113 return;
114
115 const char *name = type == SRC_WIN ? SRC_NAME : DISASSEM_NAME;
116 applied_layout->replace_window (tui_win_list[other]->name (), name);
117 tui_apply_current_layout ();
118 tui_delete_invisible_windows ();
119 }
120
121 /* Find LAYOUT in the "layouts" global and return its index. */
122
123 static size_t
124 find_layout (tui_layout_split *layout)
125 {
126 for (size_t i = 0; i < layouts.size (); ++i)
127 {
128 if (layout == layouts[i].get ())
129 return i;
130 }
131 gdb_assert_not_reached (_("layout not found!?"));
132 }
133
134 /* Function to set the layout. */
135
136 static void
137 tui_apply_layout (struct cmd_list_element *command,
138 const char *args, int from_tty)
139 {
140 tui_layout_split *layout
141 = (tui_layout_split *) get_cmd_context (command);
142
143 /* Make sure the curses mode is enabled. */
144 tui_enable ();
145 tui_set_layout (layout);
146 }
147
148 /* See tui-layout.h. */
149
150 void
151 tui_next_layout ()
152 {
153 size_t index = find_layout (applied_skeleton);
154 ++index;
155 if (index == layouts.size ())
156 index = 0;
157 tui_set_layout (layouts[index].get ());
158 }
159
160 /* Implement the "layout next" command. */
161
162 static void
163 tui_next_layout_command (const char *arg, int from_tty)
164 {
165 tui_enable ();
166 tui_next_layout ();
167 }
168
169 /* See tui-layout.h. */
170
171 void
172 tui_set_initial_layout ()
173 {
174 tui_set_layout (layouts[0].get ());
175 }
176
177 /* Implement the "layout prev" command. */
178
179 static void
180 tui_prev_layout_command (const char *arg, int from_tty)
181 {
182 tui_enable ();
183 size_t index = find_layout (applied_skeleton);
184 if (index == 0)
185 index = layouts.size ();
186 --index;
187 tui_set_layout (layouts[index].get ());
188 }
189
190
191 /* See tui-layout.h. */
192
193 void
194 tui_regs_layout ()
195 {
196 /* If there's already a register window, we're done. */
197 if (TUI_DATA_WIN != nullptr)
198 return;
199
200 tui_set_layout (TUI_DISASM_WIN != nullptr
201 ? asm_regs_layout
202 : src_regs_layout);
203 }
204
205 /* Implement the "layout regs" command. */
206
207 static void
208 tui_regs_layout_command (const char *arg, int from_tty)
209 {
210 tui_enable ();
211 tui_regs_layout ();
212 }
213
214 /* See tui-layout.h. */
215
216 void
217 tui_remove_some_windows ()
218 {
219 tui_win_info *focus = tui_win_with_focus ();
220
221 if (strcmp (focus->name (), "cmd") == 0)
222 {
223 /* Try leaving the source or disassembly window. If neither
224 exists, just do nothing. */
225 focus = TUI_SRC_WIN;
226 if (focus == nullptr)
227 focus = TUI_DISASM_WIN;
228 if (focus == nullptr)
229 return;
230 }
231
232 applied_layout->remove_windows (focus->name ());
233 tui_apply_current_layout ();
234 }
235
236 static void
237 extract_display_start_addr (struct gdbarch **gdbarch_p, CORE_ADDR *addr_p)
238 {
239 struct gdbarch *gdbarch = nullptr;
240 CORE_ADDR addr = 0;
241 CORE_ADDR pc;
242 struct symtab_and_line cursal = get_current_source_symtab_and_line ();
243
244 if (TUI_SRC_WIN != nullptr)
245 {
246 gdbarch = TUI_SRC_WIN->gdbarch;
247 find_line_pc (cursal.symtab,
248 TUI_SRC_WIN->start_line_or_addr.u.line_no,
249 &pc);
250 addr = pc;
251 }
252 else if (TUI_DISASM_WIN != nullptr)
253 {
254 gdbarch = TUI_DISASM_WIN->gdbarch;
255 addr = TUI_DISASM_WIN->start_line_or_addr.u.addr;
256 }
257
258 *gdbarch_p = gdbarch;
259 *addr_p = addr;
260 }
261
262 void
263 tui_gen_win_info::resize (int height_, int width_,
264 int origin_x_, int origin_y_)
265 {
266 if (width == width_ && height == height_
267 && x == origin_x_ && y == origin_y_
268 && handle != nullptr)
269 return;
270
271 width = width_;
272 height = height_;
273 x = origin_x_;
274 y = origin_y_;
275
276 if (handle != nullptr)
277 {
278 #ifdef HAVE_WRESIZE
279 wresize (handle.get (), height, width);
280 mvwin (handle.get (), y, x);
281 wmove (handle.get (), 0, 0);
282 #else
283 handle.reset (nullptr);
284 #endif
285 }
286
287 if (handle == nullptr)
288 make_window ();
289
290 rerender ();
291 }
292
293 \f
294
295 /* Helper function that returns a TUI window, given its name. */
296
297 static tui_gen_win_info *
298 tui_get_window_by_name (const std::string &name)
299 {
300 if (name == "src")
301 {
302 if (tui_win_list[SRC_WIN] == nullptr)
303 tui_win_list[SRC_WIN] = new tui_source_window ();
304 return tui_win_list[SRC_WIN];
305 }
306 else if (name == "cmd")
307 {
308 if (tui_win_list[CMD_WIN] == nullptr)
309 tui_win_list[CMD_WIN] = new tui_cmd_window ();
310 return tui_win_list[CMD_WIN];
311 }
312 else if (name == "regs")
313 {
314 if (tui_win_list[DATA_WIN] == nullptr)
315 tui_win_list[DATA_WIN] = new tui_data_window ();
316 return tui_win_list[DATA_WIN];
317 }
318 else if (name == "asm")
319 {
320 if (tui_win_list[DISASSEM_WIN] == nullptr)
321 tui_win_list[DISASSEM_WIN] = new tui_disasm_window ();
322 return tui_win_list[DISASSEM_WIN];
323 }
324 else
325 {
326 gdb_assert (name == "status");
327 return tui_locator_win_info_ptr ();
328 }
329 }
330
331 /* See tui-layout.h. */
332
333 std::unique_ptr<tui_layout_base>
334 tui_layout_window::clone () const
335 {
336 tui_layout_window *result = new tui_layout_window (m_contents.c_str ());
337 return std::unique_ptr<tui_layout_base> (result);
338 }
339
340 /* See tui-layout.h. */
341
342 void
343 tui_layout_window::apply (int x_, int y_, int width_, int height_)
344 {
345 x = x_;
346 y = y_;
347 width = width_;
348 height = height_;
349 gdb_assert (m_window != nullptr);
350 m_window->resize (height, width, x, y);
351 }
352
353 /* See tui-layout.h. */
354
355 void
356 tui_layout_window::get_sizes (int *min_height, int *max_height)
357 {
358 if (m_window == nullptr)
359 m_window = tui_get_window_by_name (m_contents);
360 *min_height = m_window->min_height ();
361 *max_height = m_window->max_height ();
362 }
363
364 /* See tui-layout.h. */
365
366 bool
367 tui_layout_window::top_boxed_p () const
368 {
369 gdb_assert (m_window != nullptr);
370 return m_window->can_box ();
371 }
372
373 /* See tui-layout.h. */
374
375 bool
376 tui_layout_window::bottom_boxed_p () const
377 {
378 gdb_assert (m_window != nullptr);
379 return m_window->can_box ();
380 }
381
382 /* See tui-layout.h. */
383
384 void
385 tui_layout_window::replace_window (const char *name, const char *new_window)
386 {
387 if (m_contents == name)
388 {
389 m_contents = new_window;
390 if (m_window != nullptr)
391 {
392 m_window->make_visible (false);
393 m_window = tui_get_window_by_name (m_contents);
394 }
395 }
396 }
397
398 /* See tui-layout.h. */
399
400 tui_layout_split *
401 tui_layout_split::add_split (int weight)
402 {
403 tui_layout_split *result = new tui_layout_split ();
404 split s = {weight, std::unique_ptr<tui_layout_base> (result)};
405 m_splits.push_back (std::move (s));
406 return result;
407 }
408
409 /* See tui-layout.h. */
410
411 void
412 tui_layout_split::add_window (const char *name, int weight)
413 {
414 tui_layout_window *result = new tui_layout_window (name);
415 split s = {weight, std::unique_ptr<tui_layout_base> (result)};
416 m_splits.push_back (std::move (s));
417 }
418
419 /* See tui-layout.h. */
420
421 std::unique_ptr<tui_layout_base>
422 tui_layout_split::clone () const
423 {
424 tui_layout_split *result = new tui_layout_split ();
425 for (const split &item : m_splits)
426 {
427 std::unique_ptr<tui_layout_base> next = item.layout->clone ();
428 split s = {item.weight, std::move (next)};
429 result->m_splits.push_back (std::move (s));
430 }
431 return std::unique_ptr<tui_layout_base> (result);
432 }
433
434 /* See tui-layout.h. */
435
436 void
437 tui_layout_split::get_sizes (int *min_height, int *max_height)
438 {
439 *min_height = 0;
440 *max_height = 0;
441 for (const split &item : m_splits)
442 {
443 int new_min, new_max;
444 item.layout->get_sizes (&new_min, &new_max);
445 *min_height += new_min;
446 *max_height += new_max;
447 }
448 }
449
450 /* See tui-layout.h. */
451
452 bool
453 tui_layout_split::top_boxed_p () const
454 {
455 if (m_splits.empty ())
456 return false;
457 return m_splits[0].layout->top_boxed_p ();
458 }
459
460 /* See tui-layout.h. */
461
462 bool
463 tui_layout_split::bottom_boxed_p () const
464 {
465 if (m_splits.empty ())
466 return false;
467 return m_splits.back ().layout->top_boxed_p ();
468 }
469
470 /* See tui-layout.h. */
471
472 void
473 tui_layout_split::set_weights_from_heights ()
474 {
475 for (int i = 0; i < m_splits.size (); ++i)
476 m_splits[i].weight = m_splits[i].layout->height;
477 }
478
479 /* See tui-layout.h. */
480
481 bool
482 tui_layout_split::adjust_size (const char *name, int new_height)
483 {
484 /* Look through the children. If one is a layout holding the named
485 window, we're done; or if one actually is the named window,
486 update it. */
487 int found_index = -1;
488 for (int i = 0; i < m_splits.size (); ++i)
489 {
490 if (m_splits[i].layout->adjust_size (name, new_height))
491 return true;
492 const char *win_name = m_splits[i].layout->get_name ();
493 if (win_name != nullptr && strcmp (name, win_name) == 0)
494 {
495 found_index = i;
496 break;
497 }
498 }
499
500 if (found_index == -1)
501 return false;
502 if (m_splits[found_index].layout->height == new_height)
503 return true;
504
505 set_weights_from_heights ();
506 int delta = m_splits[found_index].weight - new_height;
507 m_splits[found_index].weight = new_height;
508
509 /* Distribute the "delta" over the next window; but if the next
510 window cannot hold it all, keep going until we either find a
511 window that does, or until we loop all the way around. */
512 for (int i = 0; delta != 0 && i < m_splits.size () - 1; ++i)
513 {
514 int index = (found_index + 1 + i) % m_splits.size ();
515
516 int new_min, new_max;
517 m_splits[index].layout->get_sizes (&new_min, &new_max);
518
519 if (delta < 0)
520 {
521 /* The primary window grew, so we are trying to shrink other
522 windows. */
523 int available = m_splits[index].weight - new_min;
524 int shrink_by = std::min (available, -delta);
525 m_splits[index].weight -= shrink_by;
526 delta += shrink_by;
527 }
528 else
529 {
530 /* The primary window shrank, so we are trying to grow other
531 windows. */
532 int available = new_max - m_splits[index].weight;
533 int grow_by = std::min (available, delta);
534 m_splits[index].weight += grow_by;
535 delta -= grow_by;
536 }
537 }
538
539 if (delta != 0)
540 {
541 warning (_("Invalid window height specified"));
542 /* Effectively undo any modifications made here. */
543 set_weights_from_heights ();
544 }
545 else
546 {
547 /* Simply re-apply the updated layout. */
548 apply (x, y, width, height);
549 }
550
551 return true;
552 }
553
554 /* See tui-layout.h. */
555
556 void
557 tui_layout_split::apply (int x_, int y_, int width_, int height_)
558 {
559 x = x_;
560 y = y_;
561 width = width_;
562 height = height_;
563
564 struct height_info
565 {
566 int height;
567 int min_height;
568 int max_height;
569 /* True if this window will share a box border with the previous
570 window in the list. */
571 bool share_box;
572 };
573
574 std::vector<height_info> info (m_splits.size ());
575
576 /* Step 1: Find the min and max height of each sub-layout.
577 Fixed-sized layouts are given their desired height, and then the
578 remaining space is distributed among the remaining windows
579 according to the weights given. */
580 int available_height = height;
581 int last_index = -1;
582 int total_weight = 0;
583 for (int i = 0; i < m_splits.size (); ++i)
584 {
585 bool cmd_win_already_exists = TUI_CMD_WIN != nullptr;
586
587 /* Always call get_sizes, to ensure that the window is
588 instantiated. This is a bit gross but less gross than adding
589 special cases for this in other places. */
590 m_splits[i].layout->get_sizes (&info[i].min_height, &info[i].max_height);
591
592 if (!m_applied
593 && cmd_win_already_exists
594 && m_splits[i].layout->get_name () != nullptr
595 && strcmp (m_splits[i].layout->get_name (), "cmd") == 0)
596 {
597 /* If this layout has never been applied, then it means the
598 user just changed the layout. In this situation, it's
599 desirable to keep the size of the command window the
600 same. Setting the min and max heights this way ensures
601 that the resizing step, below, does the right thing with
602 this window. */
603 info[i].min_height = TUI_CMD_WIN->height;
604 info[i].max_height = TUI_CMD_WIN->height;
605 }
606
607 if (info[i].min_height == info[i].max_height)
608 available_height -= info[i].min_height;
609 else
610 {
611 last_index = i;
612 total_weight += m_splits[i].weight;
613 }
614
615 /* Two adjacent boxed windows will share a border, making a bit
616 more height available. */
617 if (i > 0
618 && m_splits[i - 1].layout->bottom_boxed_p ()
619 && m_splits[i].layout->top_boxed_p ())
620 info[i].share_box = true;
621 }
622
623 /* Step 2: Compute the height of each sub-layout. Fixed-sized items
624 are given their fixed size, while others are resized according to
625 their weight. */
626 int used_height = 0;
627 for (int i = 0; i < m_splits.size (); ++i)
628 {
629 /* Compute the height and clamp to the allowable range. */
630 info[i].height = available_height * m_splits[i].weight / total_weight;
631 if (info[i].height > info[i].max_height)
632 info[i].height = info[i].max_height;
633 if (info[i].height < info[i].min_height)
634 info[i].height = info[i].min_height;
635 /* If there is any leftover height, just redistribute it to the
636 last resizeable window, by dropping it from the allocated
637 height. We could try to be fancier here perhaps, by
638 redistributing this height among all windows, not just the
639 last window. */
640 if (info[i].min_height != info[i].max_height)
641 {
642 used_height += info[i].height;
643 if (info[i].share_box)
644 --used_height;
645 }
646 }
647
648 /* Allocate any leftover height. */
649 if (available_height >= used_height && last_index != -1)
650 info[last_index].height += available_height - used_height;
651
652 /* Step 3: Resize. */
653 int height_accum = 0;
654 for (int i = 0; i < m_splits.size (); ++i)
655 {
656 /* If we fall off the bottom, just make allocations overlap.
657 GIGO. */
658 if (height_accum + info[i].height > height)
659 height_accum = height - info[i].height;
660 else if (info[i].share_box)
661 --height_accum;
662 m_splits[i].layout->apply (x, y + height_accum, width, info[i].height);
663 height_accum += info[i].height;
664 }
665
666 m_applied = true;
667 }
668
669 /* See tui-layout.h. */
670
671 void
672 tui_layout_split::remove_windows (const char *name)
673 {
674 for (int i = 0; i < m_splits.size (); ++i)
675 {
676 const char *this_name = m_splits[i].layout->get_name ();
677 if (this_name == nullptr)
678 m_splits[i].layout->remove_windows (name);
679 else
680 {
681 if (strcmp (this_name, name) == 0
682 || strcmp (this_name, "cmd") == 0)
683 {
684 /* Keep. */
685 }
686 m_splits.erase (m_splits.begin () + i);
687 --i;
688 }
689 }
690 }
691
692 /* See tui-layout.h. */
693
694 void
695 tui_layout_split::replace_window (const char *name, const char *new_window)
696 {
697 for (auto &item : m_splits)
698 item.layout->replace_window (name, new_window);
699 }
700
701 /* Destroy the layout associated with SELF. */
702
703 static void
704 destroy_layout (struct cmd_list_element *self, void *context)
705 {
706 tui_layout_split *layout = (tui_layout_split *) context;
707 size_t index = find_layout (layout);
708 layouts.erase (layouts.begin () + index);
709 }
710
711 /* List holding the sub-commands of "layout". */
712
713 static struct cmd_list_element *layout_list;
714
715 /* Add a "layout" command with name NAME that switches to LAYOUT. */
716
717 static void
718 add_layout_command (const char *name, tui_layout_split *layout)
719 {
720 struct cmd_list_element *cmd;
721
722 gdb::unique_xmalloc_ptr<char> doc (xstrprintf (_("Apply the \"%s\" layout"),
723 name));
724
725 cmd = add_cmd (name, class_tui, nullptr, doc.get (), &layout_list);
726 set_cmd_context (cmd, layout);
727 /* There is no API to set this. */
728 cmd->func = tui_apply_layout;
729 cmd->destroyer = destroy_layout;
730 cmd->doc_allocated = 1;
731 doc.release ();
732 layouts.emplace_back (layout);
733 }
734
735 /* Initialize the standard layouts. */
736
737 static void
738 initialize_layouts ()
739 {
740 tui_layout_split *layout;
741
742 layout = new tui_layout_split ();
743 layout->add_window ("src", 2);
744 layout->add_window ("status", 0);
745 layout->add_window ("cmd", 1);
746 add_layout_command ("src", layout);
747
748 layout = new tui_layout_split ();
749 layout->add_window ("asm", 2);
750 layout->add_window ("status", 0);
751 layout->add_window ("cmd", 1);
752 add_layout_command ("asm", layout);
753
754 layout = new tui_layout_split ();
755 layout->add_window ("src", 1);
756 layout->add_window ("asm", 1);
757 layout->add_window ("status", 0);
758 layout->add_window ("cmd", 1);
759 add_layout_command ("split", layout);
760
761 layout = new tui_layout_split ();
762 layout->add_window ("regs", 1);
763 layout->add_window ("src", 1);
764 layout->add_window ("status", 0);
765 layout->add_window ("cmd", 1);
766 layouts.emplace_back (layout);
767 src_regs_layout = layout;
768
769 layout = new tui_layout_split ();
770 layout->add_window ("regs", 1);
771 layout->add_window ("asm", 1);
772 layout->add_window ("status", 0);
773 layout->add_window ("cmd", 1);
774 layouts.emplace_back (layout);
775 asm_regs_layout = layout;
776 }
777
778 \f
779
780 /* Base command for "layout". */
781
782 static void
783 tui_layout_command (const char *layout_name, int from_tty)
784 {
785 help_list (layout_list, "layout ", all_commands, gdb_stdout);
786 }
787
788 /* Function to initialize gdb commands, for tui window layout
789 manipulation. */
790
791 void _initialize_tui_layout ();
792 void
793 _initialize_tui_layout ()
794 {
795 add_prefix_cmd ("layout", class_tui, tui_layout_command, _("\
796 Change the layout of windows.\n\
797 Usage: layout prev | next | LAYOUT-NAME"),
798 &layout_list, "layout ", 0, &cmdlist);
799
800 add_cmd ("next", class_tui, tui_next_layout_command,
801 _("Apply the next TUI layout"),
802 &layout_list);
803 add_cmd ("prev", class_tui, tui_prev_layout_command,
804 _("Apply the previous TUI layout"),
805 &layout_list);
806 add_cmd ("regs", class_tui, tui_regs_layout_command,
807 _("Apply the TUI register layout"),
808 &layout_list);
809
810 initialize_layouts ();
811 }
This page took 0.045777 seconds and 5 git commands to generate.