Commit | Line | Data |
---|---|---|
f377b406 | 1 | /* TUI layout window management. |
f33c6cbf | 2 | |
3666a048 | 3 | Copyright (C) 1998-2021 Free Software Foundation, Inc. |
f33c6cbf | 4 | |
f377b406 | 5 | Contributed by Hewlett-Packard Company. |
c906108c | 6 | |
f377b406 SC |
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 | |
a9762ec7 | 11 | the Free Software Foundation; either version 3 of the License, or |
f377b406 SC |
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 | |
a9762ec7 | 20 | along with this program. If not, see <http://www.gnu.org/licenses/>. */ |
c906108c SS |
21 | |
22 | #include "defs.h" | |
957b8b5a | 23 | #include "arch-utils.h" |
c906108c SS |
24 | #include "command.h" |
25 | #include "symtab.h" | |
26 | #include "frame.h" | |
52575520 | 27 | #include "source.h" |
416eb92d TT |
28 | #include "cli/cli-cmds.h" |
29 | #include "cli/cli-decode.h" | |
ee325b61 | 30 | #include "cli/cli-utils.h" |
84b1e7c7 | 31 | #include <ctype.h> |
0240c8f1 | 32 | #include <unordered_map> |
ee325b61 | 33 | #include <unordered_set> |
c906108c | 34 | |
d7b2e967 | 35 | #include "tui/tui.h" |
ce38393b | 36 | #include "tui/tui-command.h" |
d7b2e967 | 37 | #include "tui/tui-data.h" |
d7b2e967 AC |
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" | |
2c0b251b | 44 | #include "tui/tui-layout.h" |
bfad4537 | 45 | #include "tui/tui-source.h" |
6a83354a | 46 | #include "gdb_curses.h" |
96ec9981 | 47 | |
13274fc3 | 48 | static void extract_display_start_addr (struct gdbarch **, CORE_ADDR *); |
c906108c | 49 | |
416eb92d TT |
50 | /* The layouts. */ |
51 | static std::vector<std::unique_ptr<tui_layout_split>> layouts; | |
2192a9d3 TT |
52 | |
53 | /* The layout that is currently applied. */ | |
54 | static std::unique_ptr<tui_layout_base> applied_layout; | |
55 | ||
416eb92d TT |
56 | /* The "skeleton" version of the layout that is currently applied. */ |
57 | static tui_layout_split *applied_skeleton; | |
62cf57fe | 58 | |
416eb92d TT |
59 | /* The two special "regs" layouts. Note that these aren't registered |
60 | as commands and so can never be deleted. */ | |
61 | static tui_layout_split *src_regs_layout; | |
62 | static tui_layout_split *asm_regs_layout; | |
62cf57fe | 63 | |
7eed1a8e TT |
64 | /* See tui-data.h. */ |
65 | std::vector<tui_win_info *> tui_windows; | |
66 | ||
01b1af32 TT |
67 | /* When applying a layout, this is the list of all windows that were |
68 | in the previous layout. This is used to re-use windows when | |
69 | changing a layout. */ | |
70 | static std::vector<tui_win_info *> saved_tui_windows; | |
71 | ||
2192a9d3 TT |
72 | /* See tui-layout.h. */ |
73 | ||
74 | void | |
75 | tui_apply_current_layout () | |
76 | { | |
865a5aec TT |
77 | struct gdbarch *gdbarch; |
78 | CORE_ADDR addr; | |
79 | ||
80 | extract_display_start_addr (&gdbarch, &addr); | |
81 | ||
01b1af32 | 82 | saved_tui_windows = std::move (tui_windows); |
7eed1a8e | 83 | tui_windows.clear (); |
865a5aec | 84 | |
01b1af32 | 85 | for (tui_win_info *win_info : saved_tui_windows) |
865a5aec TT |
86 | win_info->make_visible (false); |
87 | ||
2192a9d3 | 88 | applied_layout->apply (0, 0, tui_term_width (), tui_term_height ()); |
865a5aec TT |
89 | |
90 | /* Keep the list of internal windows up-to-date. */ | |
91 | for (int win_type = SRC_WIN; (win_type < MAX_MAJOR_WINDOWS); win_type++) | |
92 | if (tui_win_list[win_type] != nullptr | |
93 | && !tui_win_list[win_type]->is_visible ()) | |
94 | tui_win_list[win_type] = nullptr; | |
95 | ||
96 | /* This should always be made visible by a layout. */ | |
97 | gdb_assert (TUI_CMD_WIN->is_visible ()); | |
98 | ||
99 | /* Now delete any window that was not re-applied. */ | |
100 | tui_win_info *focus = tui_win_with_focus (); | |
01b1af32 | 101 | for (tui_win_info *win_info : saved_tui_windows) |
865a5aec TT |
102 | { |
103 | if (!win_info->is_visible ()) | |
104 | { | |
105 | if (focus == win_info) | |
106 | tui_set_win_focus_to (tui_windows[0]); | |
865a5aec TT |
107 | } |
108 | } | |
109 | ||
110 | if (gdbarch == nullptr && TUI_DISASM_WIN != nullptr) | |
111 | tui_get_begin_asm_address (&gdbarch, &addr); | |
112 | tui_update_source_windows_with_addr (gdbarch, addr); | |
01b1af32 TT |
113 | |
114 | saved_tui_windows.clear (); | |
2192a9d3 | 115 | } |
c906108c | 116 | |
d4eeccfe TT |
117 | /* See tui-layout. */ |
118 | ||
119 | void | |
120 | tui_adjust_window_height (struct tui_win_info *win, int new_height) | |
121 | { | |
122 | applied_layout->adjust_size (win->name (), new_height); | |
123 | } | |
124 | ||
416eb92d | 125 | /* Set the current layout to LAYOUT. */ |
c906108c | 126 | |
416eb92d TT |
127 | static void |
128 | tui_set_layout (tui_layout_split *layout) | |
c906108c | 129 | { |
416eb92d TT |
130 | applied_skeleton = layout; |
131 | applied_layout = layout->clone (); | |
132 | tui_apply_current_layout (); | |
bc712bbf | 133 | } |
c906108c | 134 | |
59b8b5d2 TT |
135 | /* See tui-layout.h. */ |
136 | ||
c906108c | 137 | void |
080ce8c0 | 138 | tui_add_win_to_layout (enum tui_win_type type) |
c906108c | 139 | { |
59b8b5d2 TT |
140 | gdb_assert (type == SRC_WIN || type == DISASSEM_WIN); |
141 | ||
416eb92d TT |
142 | /* If the window already exists, no need to add it. */ |
143 | if (tui_win_list[type] != nullptr) | |
144 | return; | |
145 | ||
146 | /* If the window we are trying to replace doesn't exist, we're | |
147 | done. */ | |
148 | enum tui_win_type other = type == SRC_WIN ? DISASSEM_WIN : SRC_WIN; | |
149 | if (tui_win_list[other] == nullptr) | |
150 | return; | |
151 | ||
152 | const char *name = type == SRC_WIN ? SRC_NAME : DISASSEM_NAME; | |
153 | applied_layout->replace_window (tui_win_list[other]->name (), name); | |
154 | tui_apply_current_layout (); | |
416eb92d TT |
155 | } |
156 | ||
157 | /* Find LAYOUT in the "layouts" global and return its index. */ | |
c906108c | 158 | |
416eb92d TT |
159 | static size_t |
160 | find_layout (tui_layout_split *layout) | |
161 | { | |
162 | for (size_t i = 0; i < layouts.size (); ++i) | |
c906108c | 163 | { |
416eb92d TT |
164 | if (layout == layouts[i].get ()) |
165 | return i; | |
c906108c | 166 | } |
416eb92d | 167 | gdb_assert_not_reached (_("layout not found!?")); |
6ba8e26f | 168 | } |
c906108c | 169 | |
416eb92d | 170 | /* Function to set the layout. */ |
a0145030 | 171 | |
eb3ff9a5 | 172 | static void |
416eb92d TT |
173 | tui_apply_layout (struct cmd_list_element *command, |
174 | const char *args, int from_tty) | |
a0145030 | 175 | { |
416eb92d TT |
176 | tui_layout_split *layout |
177 | = (tui_layout_split *) get_cmd_context (command); | |
a0145030 | 178 | |
416eb92d TT |
179 | /* Make sure the curses mode is enabled. */ |
180 | tui_enable (); | |
181 | tui_set_layout (layout); | |
a0145030 AB |
182 | } |
183 | ||
416eb92d | 184 | /* See tui-layout.h. */ |
c906108c | 185 | |
416eb92d TT |
186 | void |
187 | tui_next_layout () | |
188 | { | |
189 | size_t index = find_layout (applied_skeleton); | |
190 | ++index; | |
191 | if (index == layouts.size ()) | |
192 | index = 0; | |
193 | tui_set_layout (layouts[index].get ()); | |
194 | } | |
c906108c | 195 | |
416eb92d | 196 | /* Implement the "layout next" command. */ |
c906108c | 197 | |
416eb92d TT |
198 | static void |
199 | tui_next_layout_command (const char *arg, int from_tty) | |
200 | { | |
0379b883 | 201 | tui_enable (); |
416eb92d | 202 | tui_next_layout (); |
e8b915dc | 203 | } |
c906108c | 204 | |
427326a8 TT |
205 | /* See tui-layout.h. */ |
206 | ||
207 | void | |
416eb92d TT |
208 | tui_set_initial_layout () |
209 | { | |
210 | tui_set_layout (layouts[0].get ()); | |
211 | } | |
212 | ||
213 | /* Implement the "layout prev" command. */ | |
214 | ||
215 | static void | |
216 | tui_prev_layout_command (const char *arg, int from_tty) | |
427326a8 | 217 | { |
416eb92d TT |
218 | tui_enable (); |
219 | size_t index = find_layout (applied_skeleton); | |
220 | if (index == 0) | |
221 | index = layouts.size (); | |
222 | --index; | |
223 | tui_set_layout (layouts[index].get ()); | |
427326a8 | 224 | } |
c906108c | 225 | |
416eb92d | 226 | |
5afe342e TT |
227 | /* See tui-layout.h. */ |
228 | ||
0dbc2fc7 TT |
229 | void |
230 | tui_regs_layout () | |
231 | { | |
416eb92d TT |
232 | /* If there's already a register window, we're done. */ |
233 | if (TUI_DATA_WIN != nullptr) | |
234 | return; | |
235 | ||
236 | tui_set_layout (TUI_DISASM_WIN != nullptr | |
237 | ? asm_regs_layout | |
238 | : src_regs_layout); | |
239 | } | |
240 | ||
241 | /* Implement the "layout regs" command. */ | |
242 | ||
243 | static void | |
244 | tui_regs_layout_command (const char *arg, int from_tty) | |
245 | { | |
246 | tui_enable (); | |
247 | tui_regs_layout (); | |
0dbc2fc7 TT |
248 | } |
249 | ||
250 | /* See tui-layout.h. */ | |
251 | ||
5afe342e TT |
252 | void |
253 | tui_remove_some_windows () | |
254 | { | |
255 | tui_win_info *focus = tui_win_with_focus (); | |
256 | ||
de543742 | 257 | if (strcmp (focus->name (), CMD_NAME) == 0) |
5afe342e TT |
258 | { |
259 | /* Try leaving the source or disassembly window. If neither | |
260 | exists, just do nothing. */ | |
261 | focus = TUI_SRC_WIN; | |
262 | if (focus == nullptr) | |
263 | focus = TUI_DISASM_WIN; | |
264 | if (focus == nullptr) | |
265 | return; | |
266 | } | |
267 | ||
268 | applied_layout->remove_windows (focus->name ()); | |
269 | tui_apply_current_layout (); | |
270 | } | |
271 | ||
13274fc3 UW |
272 | static void |
273 | extract_display_start_addr (struct gdbarch **gdbarch_p, CORE_ADDR *addr_p) | |
c906108c | 274 | { |
416eb92d | 275 | if (TUI_SRC_WIN != nullptr) |
432b5c40 | 276 | TUI_SRC_WIN->display_start_addr (gdbarch_p, addr_p); |
416eb92d | 277 | else if (TUI_DISASM_WIN != nullptr) |
432b5c40 TT |
278 | TUI_DISASM_WIN->display_start_addr (gdbarch_p, addr_p); |
279 | else | |
416eb92d | 280 | { |
432b5c40 TT |
281 | *gdbarch_p = nullptr; |
282 | *addr_p = 0; | |
c906108c | 283 | } |
6ba8e26f | 284 | } |
c906108c | 285 | |
d6ba6a11 | 286 | void |
32c1e210 TT |
287 | tui_win_info::resize (int height_, int width_, |
288 | int origin_x_, int origin_y_) | |
c906108c | 289 | { |
cdaa6eb4 | 290 | if (width == width_ && height == height_ |
fb3184d8 | 291 | && x == origin_x_ && y == origin_y_ |
cdaa6eb4 TT |
292 | && handle != nullptr) |
293 | return; | |
294 | ||
d6ba6a11 | 295 | width = width_; |
db502012 | 296 | height = height_; |
fb3184d8 TT |
297 | x = origin_x_; |
298 | y = origin_y_; | |
db502012 TT |
299 | |
300 | if (handle != nullptr) | |
301 | { | |
302 | #ifdef HAVE_WRESIZE | |
7523da63 | 303 | wresize (handle.get (), height, width); |
fb3184d8 | 304 | mvwin (handle.get (), y, x); |
7523da63 | 305 | wmove (handle.get (), 0, 0); |
db502012 | 306 | #else |
7523da63 | 307 | handle.reset (nullptr); |
db502012 TT |
308 | #endif |
309 | } | |
310 | ||
311 | if (handle == nullptr) | |
ab0e1f1a | 312 | make_window (); |
3df505f6 TT |
313 | |
314 | rerender (); | |
d6ba6a11 | 315 | } |
c906108c | 316 | |
d9fcefd5 TT |
317 | \f |
318 | ||
0240c8f1 TT |
319 | /* Helper function to create one of the built-in (non-locator) |
320 | windows. */ | |
321 | ||
322 | template<enum tui_win_type V, class T> | |
32c1e210 | 323 | static tui_win_info * |
0240c8f1 TT |
324 | make_standard_window (const char *) |
325 | { | |
326 | if (tui_win_list[V] == nullptr) | |
327 | tui_win_list[V] = new T (); | |
328 | return tui_win_list[V]; | |
329 | } | |
330 | ||
0240c8f1 TT |
331 | /* A map holding all the known window types, keyed by name. Note that |
332 | this is heap-allocated and "leaked" at gdb exit. This avoids | |
333 | ordering issues with destroying elements in the map at shutdown. | |
334 | In particular, destroying this map can occur after Python has been | |
335 | shut down, causing crashes if any window destruction requires | |
336 | running Python code. */ | |
337 | ||
338 | static std::unordered_map<std::string, window_factory> *known_window_types; | |
339 | ||
389e7ddb TT |
340 | /* Helper function that returns a TUI window, given its name. */ |
341 | ||
32c1e210 | 342 | static tui_win_info * |
389e7ddb TT |
343 | tui_get_window_by_name (const std::string &name) |
344 | { | |
0240c8f1 TT |
345 | for (tui_win_info *window : saved_tui_windows) |
346 | if (name == window->name ()) | |
347 | return window; | |
348 | ||
349 | auto iter = known_window_types->find (name); | |
350 | if (iter == known_window_types->end ()) | |
351 | error (_("Unknown window type \"%s\""), name.c_str ()); | |
352 | ||
32c1e210 | 353 | tui_win_info *result = iter->second (name.c_str ()); |
0240c8f1 TT |
354 | if (result == nullptr) |
355 | error (_("Could not create window \"%s\""), name.c_str ()); | |
356 | return result; | |
357 | } | |
358 | ||
359 | /* Initialize the known window types. */ | |
360 | ||
361 | static void | |
362 | initialize_known_windows () | |
363 | { | |
364 | known_window_types = new std::unordered_map<std::string, window_factory>; | |
365 | ||
de543742 | 366 | known_window_types->emplace (SRC_NAME, |
0240c8f1 TT |
367 | make_standard_window<SRC_WIN, |
368 | tui_source_window>); | |
de543742 | 369 | known_window_types->emplace (CMD_NAME, |
0240c8f1 | 370 | make_standard_window<CMD_WIN, tui_cmd_window>); |
de543742 | 371 | known_window_types->emplace (DATA_NAME, |
0240c8f1 TT |
372 | make_standard_window<DATA_WIN, |
373 | tui_data_window>); | |
de543742 | 374 | known_window_types->emplace (DISASSEM_NAME, |
0240c8f1 TT |
375 | make_standard_window<DISASSEM_WIN, |
376 | tui_disasm_window>); | |
f237f998 AB |
377 | known_window_types->emplace (STATUS_NAME, |
378 | make_standard_window<STATUS_WIN, | |
379 | tui_locator_window>); | |
389e7ddb TT |
380 | } |
381 | ||
382 | /* See tui-layout.h. */ | |
383 | ||
01b1af32 TT |
384 | void |
385 | tui_register_window (const char *name, window_factory &&factory) | |
386 | { | |
387 | std::string name_copy = name; | |
388 | ||
de543742 TT |
389 | if (name_copy == SRC_NAME || name_copy == CMD_NAME || name_copy == DATA_NAME |
390 | || name_copy == DISASSEM_NAME || name_copy == STATUS_NAME) | |
01b1af32 TT |
391 | error (_("Window type \"%s\" is built-in"), name); |
392 | ||
393 | known_window_types->emplace (std::move (name_copy), | |
394 | std::move (factory)); | |
395 | } | |
396 | ||
397 | /* See tui-layout.h. */ | |
398 | ||
389e7ddb TT |
399 | std::unique_ptr<tui_layout_base> |
400 | tui_layout_window::clone () const | |
401 | { | |
402 | tui_layout_window *result = new tui_layout_window (m_contents.c_str ()); | |
403 | return std::unique_ptr<tui_layout_base> (result); | |
404 | } | |
405 | ||
406 | /* See tui-layout.h. */ | |
407 | ||
408 | void | |
409 | tui_layout_window::apply (int x_, int y_, int width_, int height_) | |
410 | { | |
411 | x = x_; | |
412 | y = y_; | |
413 | width = width_; | |
414 | height = height_; | |
415 | gdb_assert (m_window != nullptr); | |
416 | m_window->resize (height, width, x, y); | |
32c1e210 | 417 | tui_windows.push_back (m_window); |
389e7ddb TT |
418 | } |
419 | ||
420 | /* See tui-layout.h. */ | |
421 | ||
422 | void | |
7c043ba6 | 423 | tui_layout_window::get_sizes (bool height, int *min_value, int *max_value) |
389e7ddb TT |
424 | { |
425 | if (m_window == nullptr) | |
426 | m_window = tui_get_window_by_name (m_contents); | |
7c043ba6 TT |
427 | if (height) |
428 | { | |
429 | *min_value = m_window->min_height (); | |
430 | *max_value = m_window->max_height (); | |
431 | } | |
432 | else | |
433 | { | |
434 | *min_value = m_window->min_width (); | |
435 | *max_value = m_window->max_width (); | |
436 | } | |
389e7ddb TT |
437 | } |
438 | ||
439 | /* See tui-layout.h. */ | |
440 | ||
441 | bool | |
442 | tui_layout_window::top_boxed_p () const | |
443 | { | |
444 | gdb_assert (m_window != nullptr); | |
445 | return m_window->can_box (); | |
446 | } | |
447 | ||
448 | /* See tui-layout.h. */ | |
449 | ||
450 | bool | |
451 | tui_layout_window::bottom_boxed_p () const | |
452 | { | |
453 | gdb_assert (m_window != nullptr); | |
454 | return m_window->can_box (); | |
455 | } | |
456 | ||
457 | /* See tui-layout.h. */ | |
458 | ||
416eb92d TT |
459 | void |
460 | tui_layout_window::replace_window (const char *name, const char *new_window) | |
461 | { | |
462 | if (m_contents == name) | |
463 | { | |
464 | m_contents = new_window; | |
465 | if (m_window != nullptr) | |
466 | { | |
467 | m_window->make_visible (false); | |
468 | m_window = tui_get_window_by_name (m_contents); | |
469 | } | |
470 | } | |
471 | } | |
472 | ||
473 | /* See tui-layout.h. */ | |
474 | ||
ee325b61 | 475 | void |
c22fef7e | 476 | tui_layout_window::specification (ui_file *output, int depth) |
ee325b61 TT |
477 | { |
478 | fputs_unfiltered (get_name (), output); | |
479 | } | |
480 | ||
481 | /* See tui-layout.h. */ | |
482 | ||
c22fef7e TT |
483 | void |
484 | tui_layout_split::add_split (std::unique_ptr<tui_layout_split> &&layout, | |
485 | int weight) | |
389e7ddb | 486 | { |
c22fef7e | 487 | split s = {weight, std::move (layout)}; |
389e7ddb | 488 | m_splits.push_back (std::move (s)); |
389e7ddb TT |
489 | } |
490 | ||
491 | /* See tui-layout.h. */ | |
492 | ||
493 | void | |
494 | tui_layout_split::add_window (const char *name, int weight) | |
495 | { | |
496 | tui_layout_window *result = new tui_layout_window (name); | |
497 | split s = {weight, std::unique_ptr<tui_layout_base> (result)}; | |
498 | m_splits.push_back (std::move (s)); | |
499 | } | |
500 | ||
501 | /* See tui-layout.h. */ | |
502 | ||
503 | std::unique_ptr<tui_layout_base> | |
504 | tui_layout_split::clone () const | |
505 | { | |
7c043ba6 | 506 | tui_layout_split *result = new tui_layout_split (m_vertical); |
389e7ddb TT |
507 | for (const split &item : m_splits) |
508 | { | |
509 | std::unique_ptr<tui_layout_base> next = item.layout->clone (); | |
510 | split s = {item.weight, std::move (next)}; | |
511 | result->m_splits.push_back (std::move (s)); | |
512 | } | |
513 | return std::unique_ptr<tui_layout_base> (result); | |
514 | } | |
515 | ||
516 | /* See tui-layout.h. */ | |
517 | ||
518 | void | |
7c043ba6 | 519 | tui_layout_split::get_sizes (bool height, int *min_value, int *max_value) |
389e7ddb | 520 | { |
7c043ba6 TT |
521 | *min_value = 0; |
522 | *max_value = 0; | |
523 | bool first_time = true; | |
389e7ddb TT |
524 | for (const split &item : m_splits) |
525 | { | |
526 | int new_min, new_max; | |
7c043ba6 TT |
527 | item.layout->get_sizes (height, &new_min, &new_max); |
528 | /* For the mismatch case, the first time through we want to set | |
529 | the min and max to the computed values -- the "first_time" | |
530 | check here is just a funny way of doing that. */ | |
531 | if (height == m_vertical || first_time) | |
532 | { | |
533 | *min_value += new_min; | |
534 | *max_value += new_max; | |
535 | } | |
536 | else | |
537 | { | |
538 | *min_value = std::max (*min_value, new_min); | |
539 | *max_value = std::min (*max_value, new_max); | |
540 | } | |
541 | first_time = false; | |
389e7ddb TT |
542 | } |
543 | } | |
544 | ||
545 | /* See tui-layout.h. */ | |
546 | ||
547 | bool | |
548 | tui_layout_split::top_boxed_p () const | |
549 | { | |
550 | if (m_splits.empty ()) | |
551 | return false; | |
552 | return m_splits[0].layout->top_boxed_p (); | |
553 | } | |
554 | ||
555 | /* See tui-layout.h. */ | |
556 | ||
557 | bool | |
558 | tui_layout_split::bottom_boxed_p () const | |
559 | { | |
560 | if (m_splits.empty ()) | |
561 | return false; | |
562 | return m_splits.back ().layout->top_boxed_p (); | |
563 | } | |
564 | ||
565 | /* See tui-layout.h. */ | |
566 | ||
567 | void | |
568 | tui_layout_split::set_weights_from_heights () | |
569 | { | |
570 | for (int i = 0; i < m_splits.size (); ++i) | |
571 | m_splits[i].weight = m_splits[i].layout->height; | |
572 | } | |
573 | ||
574 | /* See tui-layout.h. */ | |
575 | ||
6bc56648 | 576 | tui_adjust_result |
389e7ddb TT |
577 | tui_layout_split::adjust_size (const char *name, int new_height) |
578 | { | |
579 | /* Look through the children. If one is a layout holding the named | |
580 | window, we're done; or if one actually is the named window, | |
581 | update it. */ | |
582 | int found_index = -1; | |
583 | for (int i = 0; i < m_splits.size (); ++i) | |
584 | { | |
6bc56648 TT |
585 | tui_adjust_result adjusted |
586 | = m_splits[i].layout->adjust_size (name, new_height); | |
587 | if (adjusted == HANDLED) | |
588 | return HANDLED; | |
589 | if (adjusted == FOUND) | |
389e7ddb | 590 | { |
7c043ba6 TT |
591 | if (!m_vertical) |
592 | return FOUND; | |
389e7ddb TT |
593 | found_index = i; |
594 | break; | |
595 | } | |
596 | } | |
597 | ||
598 | if (found_index == -1) | |
6bc56648 | 599 | return NOT_FOUND; |
389e7ddb | 600 | if (m_splits[found_index].layout->height == new_height) |
6bc56648 | 601 | return HANDLED; |
389e7ddb TT |
602 | |
603 | set_weights_from_heights (); | |
604 | int delta = m_splits[found_index].weight - new_height; | |
605 | m_splits[found_index].weight = new_height; | |
606 | ||
607 | /* Distribute the "delta" over the next window; but if the next | |
608 | window cannot hold it all, keep going until we either find a | |
609 | window that does, or until we loop all the way around. */ | |
610 | for (int i = 0; delta != 0 && i < m_splits.size () - 1; ++i) | |
611 | { | |
612 | int index = (found_index + 1 + i) % m_splits.size (); | |
613 | ||
614 | int new_min, new_max; | |
7c043ba6 | 615 | m_splits[index].layout->get_sizes (m_vertical, &new_min, &new_max); |
389e7ddb TT |
616 | |
617 | if (delta < 0) | |
618 | { | |
619 | /* The primary window grew, so we are trying to shrink other | |
620 | windows. */ | |
621 | int available = m_splits[index].weight - new_min; | |
622 | int shrink_by = std::min (available, -delta); | |
623 | m_splits[index].weight -= shrink_by; | |
624 | delta += shrink_by; | |
625 | } | |
626 | else | |
627 | { | |
628 | /* The primary window shrank, so we are trying to grow other | |
629 | windows. */ | |
630 | int available = new_max - m_splits[index].weight; | |
631 | int grow_by = std::min (available, delta); | |
632 | m_splits[index].weight += grow_by; | |
633 | delta -= grow_by; | |
634 | } | |
635 | } | |
636 | ||
637 | if (delta != 0) | |
638 | { | |
639 | warning (_("Invalid window height specified")); | |
640 | /* Effectively undo any modifications made here. */ | |
641 | set_weights_from_heights (); | |
642 | } | |
643 | else | |
644 | { | |
645 | /* Simply re-apply the updated layout. */ | |
646 | apply (x, y, width, height); | |
647 | } | |
648 | ||
6bc56648 | 649 | return HANDLED; |
389e7ddb TT |
650 | } |
651 | ||
652 | /* See tui-layout.h. */ | |
653 | ||
654 | void | |
655 | tui_layout_split::apply (int x_, int y_, int width_, int height_) | |
656 | { | |
657 | x = x_; | |
658 | y = y_; | |
659 | width = width_; | |
660 | height = height_; | |
661 | ||
7c043ba6 | 662 | struct size_info |
389e7ddb | 663 | { |
7c043ba6 TT |
664 | int size; |
665 | int min_size; | |
666 | int max_size; | |
389e7ddb TT |
667 | /* True if this window will share a box border with the previous |
668 | window in the list. */ | |
669 | bool share_box; | |
670 | }; | |
671 | ||
7c043ba6 | 672 | std::vector<size_info> info (m_splits.size ()); |
389e7ddb | 673 | |
7c043ba6 TT |
674 | /* Step 1: Find the min and max size of each sub-layout. |
675 | Fixed-sized layouts are given their desired size, and then the | |
389e7ddb TT |
676 | remaining space is distributed among the remaining windows |
677 | according to the weights given. */ | |
7c043ba6 | 678 | int available_size = m_vertical ? height : width; |
389e7ddb TT |
679 | int last_index = -1; |
680 | int total_weight = 0; | |
681 | for (int i = 0; i < m_splits.size (); ++i) | |
682 | { | |
683 | bool cmd_win_already_exists = TUI_CMD_WIN != nullptr; | |
684 | ||
685 | /* Always call get_sizes, to ensure that the window is | |
686 | instantiated. This is a bit gross but less gross than adding | |
687 | special cases for this in other places. */ | |
7c043ba6 TT |
688 | m_splits[i].layout->get_sizes (m_vertical, &info[i].min_size, |
689 | &info[i].max_size); | |
389e7ddb TT |
690 | |
691 | if (!m_applied | |
692 | && cmd_win_already_exists | |
693 | && m_splits[i].layout->get_name () != nullptr | |
694 | && strcmp (m_splits[i].layout->get_name (), "cmd") == 0) | |
695 | { | |
696 | /* If this layout has never been applied, then it means the | |
697 | user just changed the layout. In this situation, it's | |
698 | desirable to keep the size of the command window the | |
7c043ba6 | 699 | same. Setting the min and max sizes this way ensures |
389e7ddb TT |
700 | that the resizing step, below, does the right thing with |
701 | this window. */ | |
7c043ba6 TT |
702 | info[i].min_size = (m_vertical |
703 | ? TUI_CMD_WIN->height | |
704 | : TUI_CMD_WIN->width); | |
705 | info[i].max_size = info[i].min_size; | |
389e7ddb TT |
706 | } |
707 | ||
7c043ba6 TT |
708 | if (info[i].min_size == info[i].max_size) |
709 | available_size -= info[i].min_size; | |
389e7ddb TT |
710 | else |
711 | { | |
712 | last_index = i; | |
713 | total_weight += m_splits[i].weight; | |
714 | } | |
715 | ||
716 | /* Two adjacent boxed windows will share a border, making a bit | |
7c043ba6 | 717 | more size available. */ |
389e7ddb TT |
718 | if (i > 0 |
719 | && m_splits[i - 1].layout->bottom_boxed_p () | |
720 | && m_splits[i].layout->top_boxed_p ()) | |
721 | info[i].share_box = true; | |
722 | } | |
723 | ||
7c043ba6 | 724 | /* Step 2: Compute the size of each sub-layout. Fixed-sized items |
389e7ddb TT |
725 | are given their fixed size, while others are resized according to |
726 | their weight. */ | |
7c043ba6 | 727 | int used_size = 0; |
389e7ddb TT |
728 | for (int i = 0; i < m_splits.size (); ++i) |
729 | { | |
730 | /* Compute the height and clamp to the allowable range. */ | |
7c043ba6 TT |
731 | info[i].size = available_size * m_splits[i].weight / total_weight; |
732 | if (info[i].size > info[i].max_size) | |
733 | info[i].size = info[i].max_size; | |
734 | if (info[i].size < info[i].min_size) | |
735 | info[i].size = info[i].min_size; | |
736 | /* If there is any leftover size, just redistribute it to the | |
389e7ddb | 737 | last resizeable window, by dropping it from the allocated |
7c043ba6 TT |
738 | size. We could try to be fancier here perhaps, by |
739 | redistributing this size among all windows, not just the | |
389e7ddb | 740 | last window. */ |
7c043ba6 | 741 | if (info[i].min_size != info[i].max_size) |
389e7ddb | 742 | { |
7c043ba6 | 743 | used_size += info[i].size; |
389e7ddb | 744 | if (info[i].share_box) |
7c043ba6 | 745 | --used_size; |
389e7ddb TT |
746 | } |
747 | } | |
748 | ||
7c043ba6 TT |
749 | /* Allocate any leftover size. */ |
750 | if (available_size >= used_size && last_index != -1) | |
751 | info[last_index].size += available_size - used_size; | |
389e7ddb TT |
752 | |
753 | /* Step 3: Resize. */ | |
7c043ba6 TT |
754 | int size_accum = 0; |
755 | const int maximum = m_vertical ? height : width; | |
389e7ddb TT |
756 | for (int i = 0; i < m_splits.size (); ++i) |
757 | { | |
758 | /* If we fall off the bottom, just make allocations overlap. | |
759 | GIGO. */ | |
7c043ba6 TT |
760 | if (size_accum + info[i].size > maximum) |
761 | size_accum = maximum - info[i].size; | |
389e7ddb | 762 | else if (info[i].share_box) |
7c043ba6 TT |
763 | --size_accum; |
764 | if (m_vertical) | |
765 | m_splits[i].layout->apply (x, y + size_accum, width, info[i].size); | |
766 | else | |
767 | m_splits[i].layout->apply (x + size_accum, y, info[i].size, height); | |
768 | size_accum += info[i].size; | |
389e7ddb TT |
769 | } |
770 | ||
771 | m_applied = true; | |
772 | } | |
773 | ||
5afe342e TT |
774 | /* See tui-layout.h. */ |
775 | ||
776 | void | |
777 | tui_layout_split::remove_windows (const char *name) | |
778 | { | |
779 | for (int i = 0; i < m_splits.size (); ++i) | |
780 | { | |
781 | const char *this_name = m_splits[i].layout->get_name (); | |
782 | if (this_name == nullptr) | |
783 | m_splits[i].layout->remove_windows (name); | |
39ec0490 | 784 | else if (strcmp (this_name, name) == 0 |
de543742 TT |
785 | || strcmp (this_name, CMD_NAME) == 0 |
786 | || strcmp (this_name, STATUS_NAME) == 0) | |
39ec0490 TT |
787 | { |
788 | /* Keep. */ | |
789 | } | |
5afe342e TT |
790 | else |
791 | { | |
5afe342e TT |
792 | m_splits.erase (m_splits.begin () + i); |
793 | --i; | |
794 | } | |
795 | } | |
796 | } | |
797 | ||
416eb92d TT |
798 | /* See tui-layout.h. */ |
799 | ||
800 | void | |
801 | tui_layout_split::replace_window (const char *name, const char *new_window) | |
802 | { | |
803 | for (auto &item : m_splits) | |
804 | item.layout->replace_window (name, new_window); | |
805 | } | |
806 | ||
ee325b61 TT |
807 | /* See tui-layout.h. */ |
808 | ||
809 | void | |
c22fef7e | 810 | tui_layout_split::specification (ui_file *output, int depth) |
ee325b61 | 811 | { |
c22fef7e TT |
812 | if (depth > 0) |
813 | fputs_unfiltered ("{", output); | |
814 | ||
7c043ba6 TT |
815 | if (!m_vertical) |
816 | fputs_unfiltered ("-horizontal ", output); | |
817 | ||
ee325b61 TT |
818 | bool first = true; |
819 | for (auto &item : m_splits) | |
820 | { | |
821 | if (!first) | |
822 | fputs_unfiltered (" ", output); | |
823 | first = false; | |
c22fef7e | 824 | item.layout->specification (output, depth + 1); |
ee325b61 TT |
825 | fprintf_unfiltered (output, " %d", item.weight); |
826 | } | |
c22fef7e TT |
827 | |
828 | if (depth > 0) | |
829 | fputs_unfiltered ("}", output); | |
ee325b61 TT |
830 | } |
831 | ||
416eb92d TT |
832 | /* Destroy the layout associated with SELF. */ |
833 | ||
2192a9d3 | 834 | static void |
416eb92d TT |
835 | destroy_layout (struct cmd_list_element *self, void *context) |
836 | { | |
837 | tui_layout_split *layout = (tui_layout_split *) context; | |
838 | size_t index = find_layout (layout); | |
839 | layouts.erase (layouts.begin () + index); | |
840 | } | |
841 | ||
842 | /* List holding the sub-commands of "layout". */ | |
843 | ||
844 | static struct cmd_list_element *layout_list; | |
845 | ||
846 | /* Add a "layout" command with name NAME that switches to LAYOUT. */ | |
847 | ||
ee325b61 | 848 | static struct cmd_list_element * |
416eb92d | 849 | add_layout_command (const char *name, tui_layout_split *layout) |
2192a9d3 | 850 | { |
416eb92d | 851 | struct cmd_list_element *cmd; |
2192a9d3 | 852 | |
ee325b61 | 853 | string_file spec; |
c22fef7e | 854 | layout->specification (&spec, 0); |
ee325b61 TT |
855 | |
856 | gdb::unique_xmalloc_ptr<char> doc | |
857 | (xstrprintf (_("Apply the \"%s\" layout.\n\ | |
858 | This layout was created using:\n\ | |
859 | tui new-layout %s %s"), | |
860 | name, name, spec.c_str ())); | |
2192a9d3 | 861 | |
416eb92d TT |
862 | cmd = add_cmd (name, class_tui, nullptr, doc.get (), &layout_list); |
863 | set_cmd_context (cmd, layout); | |
864 | /* There is no API to set this. */ | |
865 | cmd->func = tui_apply_layout; | |
866 | cmd->destroyer = destroy_layout; | |
867 | cmd->doc_allocated = 1; | |
868 | doc.release (); | |
869 | layouts.emplace_back (layout); | |
ee325b61 TT |
870 | |
871 | return cmd; | |
416eb92d | 872 | } |
2192a9d3 | 873 | |
416eb92d | 874 | /* Initialize the standard layouts. */ |
2192a9d3 | 875 | |
416eb92d TT |
876 | static void |
877 | initialize_layouts () | |
878 | { | |
879 | tui_layout_split *layout; | |
880 | ||
881 | layout = new tui_layout_split (); | |
de543742 TT |
882 | layout->add_window (SRC_NAME, 2); |
883 | layout->add_window (STATUS_NAME, 0); | |
884 | layout->add_window (CMD_NAME, 1); | |
885 | add_layout_command (SRC_NAME, layout); | |
416eb92d TT |
886 | |
887 | layout = new tui_layout_split (); | |
de543742 TT |
888 | layout->add_window (DISASSEM_NAME, 2); |
889 | layout->add_window (STATUS_NAME, 0); | |
890 | layout->add_window (CMD_NAME, 1); | |
891 | add_layout_command (DISASSEM_NAME, layout); | |
416eb92d TT |
892 | |
893 | layout = new tui_layout_split (); | |
de543742 TT |
894 | layout->add_window (SRC_NAME, 1); |
895 | layout->add_window (DISASSEM_NAME, 1); | |
896 | layout->add_window (STATUS_NAME, 0); | |
897 | layout->add_window (CMD_NAME, 1); | |
416eb92d TT |
898 | add_layout_command ("split", layout); |
899 | ||
900 | layout = new tui_layout_split (); | |
de543742 TT |
901 | layout->add_window (DATA_NAME, 1); |
902 | layout->add_window (SRC_NAME, 1); | |
903 | layout->add_window (STATUS_NAME, 0); | |
904 | layout->add_window (CMD_NAME, 1); | |
416eb92d TT |
905 | layouts.emplace_back (layout); |
906 | src_regs_layout = layout; | |
907 | ||
908 | layout = new tui_layout_split (); | |
de543742 TT |
909 | layout->add_window (DATA_NAME, 1); |
910 | layout->add_window (DISASSEM_NAME, 1); | |
911 | layout->add_window (STATUS_NAME, 0); | |
912 | layout->add_window (CMD_NAME, 1); | |
416eb92d TT |
913 | layouts.emplace_back (layout); |
914 | asm_regs_layout = layout; | |
2192a9d3 TT |
915 | } |
916 | ||
389e7ddb TT |
917 | \f |
918 | ||
ee325b61 TT |
919 | /* A helper function that returns true if NAME is the name of an |
920 | available window. */ | |
921 | ||
922 | static bool | |
923 | validate_window_name (const std::string &name) | |
924 | { | |
0240c8f1 TT |
925 | auto iter = known_window_types->find (name); |
926 | return iter != known_window_types->end (); | |
ee325b61 TT |
927 | } |
928 | ||
929 | /* Implementation of the "tui new-layout" command. */ | |
930 | ||
931 | static void | |
932 | tui_new_layout_command (const char *spec, int from_tty) | |
933 | { | |
934 | std::string new_name = extract_arg (&spec); | |
935 | if (new_name.empty ()) | |
936 | error (_("No layout name specified")); | |
937 | if (new_name[0] == '-') | |
938 | error (_("Layout name cannot start with '-'")); | |
939 | ||
7c043ba6 TT |
940 | bool is_vertical = true; |
941 | spec = skip_spaces (spec); | |
942 | if (check_for_argument (&spec, "-horizontal")) | |
943 | is_vertical = false; | |
944 | ||
c22fef7e | 945 | std::vector<std::unique_ptr<tui_layout_split>> splits; |
7c043ba6 | 946 | splits.emplace_back (new tui_layout_split (is_vertical)); |
ee325b61 TT |
947 | std::unordered_set<std::string> seen_windows; |
948 | while (true) | |
949 | { | |
c22fef7e TT |
950 | spec = skip_spaces (spec); |
951 | if (spec[0] == '\0') | |
ee325b61 | 952 | break; |
c22fef7e TT |
953 | |
954 | if (spec[0] == '{') | |
955 | { | |
7c043ba6 TT |
956 | is_vertical = true; |
957 | spec = skip_spaces (spec + 1); | |
958 | if (check_for_argument (&spec, "-horizontal")) | |
959 | is_vertical = false; | |
960 | splits.emplace_back (new tui_layout_split (is_vertical)); | |
c22fef7e TT |
961 | continue; |
962 | } | |
963 | ||
964 | bool is_close = false; | |
965 | std::string name; | |
966 | if (spec[0] == '}') | |
967 | { | |
968 | is_close = true; | |
969 | ++spec; | |
970 | if (splits.size () == 1) | |
971 | error (_("Extra '}' in layout specification")); | |
972 | } | |
973 | else | |
974 | { | |
975 | name = extract_arg (&spec); | |
976 | if (name.empty ()) | |
977 | break; | |
978 | if (!validate_window_name (name)) | |
979 | error (_("Unknown window \"%s\""), name.c_str ()); | |
980 | if (seen_windows.find (name) != seen_windows.end ()) | |
981 | error (_("Window \"%s\" seen twice in layout"), name.c_str ()); | |
982 | } | |
983 | ||
984 | ULONGEST weight = get_ulongest (&spec, '}'); | |
ee325b61 TT |
985 | if ((int) weight != weight) |
986 | error (_("Weight out of range: %s"), pulongest (weight)); | |
c22fef7e TT |
987 | if (is_close) |
988 | { | |
989 | std::unique_ptr<tui_layout_split> last_split | |
990 | = std::move (splits.back ()); | |
991 | splits.pop_back (); | |
992 | splits.back ()->add_split (std::move (last_split), weight); | |
993 | } | |
994 | else | |
995 | { | |
996 | splits.back ()->add_window (name.c_str (), weight); | |
997 | seen_windows.insert (name); | |
998 | } | |
ee325b61 | 999 | } |
c22fef7e TT |
1000 | if (splits.size () > 1) |
1001 | error (_("Missing '}' in layout specification")); | |
ee325b61 TT |
1002 | if (seen_windows.empty ()) |
1003 | error (_("New layout does not contain any windows")); | |
de543742 TT |
1004 | if (seen_windows.find (CMD_NAME) == seen_windows.end ()) |
1005 | error (_("New layout does not contain the \"" CMD_NAME "\" window")); | |
ee325b61 TT |
1006 | |
1007 | gdb::unique_xmalloc_ptr<char> cmd_name | |
1008 | = make_unique_xstrdup (new_name.c_str ()); | |
c22fef7e | 1009 | std::unique_ptr<tui_layout_split> new_layout = std::move (splits.back ()); |
ee325b61 TT |
1010 | struct cmd_list_element *cmd |
1011 | = add_layout_command (cmd_name.get (), new_layout.get ()); | |
1012 | cmd->name_allocated = 1; | |
1013 | cmd_name.release (); | |
1014 | new_layout.release (); | |
1015 | } | |
1016 | ||
d9fcefd5 TT |
1017 | /* Function to initialize gdb commands, for tui window layout |
1018 | manipulation. */ | |
1019 | ||
6c265988 | 1020 | void _initialize_tui_layout (); |
d9fcefd5 | 1021 | void |
6c265988 | 1022 | _initialize_tui_layout () |
d9fcefd5 | 1023 | { |
0743fc83 | 1024 | add_basic_prefix_cmd ("layout", class_tui, _("\ |
d9fcefd5 | 1025 | Change the layout of windows.\n\ |
416eb92d | 1026 | Usage: layout prev | next | LAYOUT-NAME"), |
0743fc83 | 1027 | &layout_list, "layout ", 0, &cmdlist); |
416eb92d TT |
1028 | |
1029 | add_cmd ("next", class_tui, tui_next_layout_command, | |
c9af6521 | 1030 | _("Apply the next TUI layout."), |
416eb92d TT |
1031 | &layout_list); |
1032 | add_cmd ("prev", class_tui, tui_prev_layout_command, | |
c9af6521 | 1033 | _("Apply the previous TUI layout."), |
416eb92d TT |
1034 | &layout_list); |
1035 | add_cmd ("regs", class_tui, tui_regs_layout_command, | |
c9af6521 | 1036 | _("Apply the TUI register layout."), |
416eb92d | 1037 | &layout_list); |
2192a9d3 | 1038 | |
ee325b61 TT |
1039 | add_cmd ("new-layout", class_tui, tui_new_layout_command, |
1040 | _("Create a new TUI layout.\n\ | |
7c043ba6 | 1041 | Usage: tui new-layout [-horizontal] NAME WINDOW WEIGHT [WINDOW WEIGHT]...\n\ |
ee325b61 TT |
1042 | Create a new TUI layout. The new layout will be named NAME,\n\ |
1043 | and can be accessed using \"layout NAME\".\n\ | |
1044 | The windows will be displayed in the specified order.\n\ | |
c22fef7e | 1045 | A WINDOW can also be of the form:\n\ |
7c043ba6 | 1046 | { [-horizontal] NAME WEIGHT [NAME WEIGHT]... }\n\ |
c22fef7e | 1047 | This form indicates a sub-frame.\n\ |
ee325b61 TT |
1048 | Each WEIGHT is an integer, which holds the relative size\n\ |
1049 | to be allocated to the window."), | |
1050 | tui_get_cmd_list ()); | |
1051 | ||
2192a9d3 | 1052 | initialize_layouts (); |
0240c8f1 | 1053 | initialize_known_windows (); |
d9fcefd5 | 1054 | } |