15ffd83dbSDimitry Andric //===-- IOHandlerCursesGUI.cpp --------------------------------------------===//
2480093f4SDimitry Andric //
3480093f4SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4480093f4SDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
5480093f4SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6480093f4SDimitry Andric //
7480093f4SDimitry Andric //===----------------------------------------------------------------------===//
8480093f4SDimitry Andric
9480093f4SDimitry Andric #include "lldb/Core/IOHandlerCursesGUI.h"
10480093f4SDimitry Andric #include "lldb/Host/Config.h"
11480093f4SDimitry Andric
12480093f4SDimitry Andric #if LLDB_ENABLE_CURSES
13af732203SDimitry Andric #if CURSES_HAVE_NCURSES_CURSES_H
14af732203SDimitry Andric #include <ncurses/curses.h>
15af732203SDimitry Andric #include <ncurses/panel.h>
16af732203SDimitry Andric #else
17480093f4SDimitry Andric #include <curses.h>
18480093f4SDimitry Andric #include <panel.h>
19480093f4SDimitry Andric #endif
20af732203SDimitry Andric #endif
21480093f4SDimitry Andric
22480093f4SDimitry Andric #if defined(__APPLE__)
23480093f4SDimitry Andric #include <deque>
24480093f4SDimitry Andric #endif
25480093f4SDimitry Andric #include <string>
26480093f4SDimitry Andric
27480093f4SDimitry Andric #include "lldb/Core/Debugger.h"
28480093f4SDimitry Andric #include "lldb/Core/StreamFile.h"
29*5f7ddb14SDimitry Andric #include "lldb/Core/ValueObjectUpdater.h"
30480093f4SDimitry Andric #include "lldb/Host/File.h"
31480093f4SDimitry Andric #include "lldb/Utility/Predicate.h"
32480093f4SDimitry Andric #include "lldb/Utility/Status.h"
33480093f4SDimitry Andric #include "lldb/Utility/StreamString.h"
34480093f4SDimitry Andric #include "lldb/Utility/StringList.h"
35480093f4SDimitry Andric #include "lldb/lldb-forward.h"
36480093f4SDimitry Andric
37480093f4SDimitry Andric #include "lldb/Interpreter/CommandCompletions.h"
38480093f4SDimitry Andric #include "lldb/Interpreter/CommandInterpreter.h"
39480093f4SDimitry Andric
40480093f4SDimitry Andric #if LLDB_ENABLE_CURSES
41480093f4SDimitry Andric #include "lldb/Breakpoint/BreakpointLocation.h"
42480093f4SDimitry Andric #include "lldb/Core/Module.h"
43*5f7ddb14SDimitry Andric #include "lldb/Core/PluginManager.h"
44480093f4SDimitry Andric #include "lldb/Core/ValueObject.h"
45480093f4SDimitry Andric #include "lldb/Core/ValueObjectRegister.h"
46480093f4SDimitry Andric #include "lldb/Symbol/Block.h"
47480093f4SDimitry Andric #include "lldb/Symbol/Function.h"
48480093f4SDimitry Andric #include "lldb/Symbol/Symbol.h"
49480093f4SDimitry Andric #include "lldb/Symbol/VariableList.h"
50480093f4SDimitry Andric #include "lldb/Target/Process.h"
51480093f4SDimitry Andric #include "lldb/Target/RegisterContext.h"
52480093f4SDimitry Andric #include "lldb/Target/StackFrame.h"
53480093f4SDimitry Andric #include "lldb/Target/StopInfo.h"
54480093f4SDimitry Andric #include "lldb/Target/Target.h"
55480093f4SDimitry Andric #include "lldb/Target/Thread.h"
56480093f4SDimitry Andric #include "lldb/Utility/State.h"
57480093f4SDimitry Andric #endif
58480093f4SDimitry Andric
59480093f4SDimitry Andric #include "llvm/ADT/StringRef.h"
60480093f4SDimitry Andric
61480093f4SDimitry Andric #ifdef _WIN32
62480093f4SDimitry Andric #include "lldb/Host/windows/windows.h"
63480093f4SDimitry Andric #endif
64480093f4SDimitry Andric
65480093f4SDimitry Andric #include <memory>
66480093f4SDimitry Andric #include <mutex>
67480093f4SDimitry Andric
68*5f7ddb14SDimitry Andric #include <cassert>
69*5f7ddb14SDimitry Andric #include <cctype>
70*5f7ddb14SDimitry Andric #include <cerrno>
71*5f7ddb14SDimitry Andric #include <cstdint>
72*5f7ddb14SDimitry Andric #include <cstdio>
73*5f7ddb14SDimitry Andric #include <cstring>
74*5f7ddb14SDimitry Andric #include <functional>
75480093f4SDimitry Andric #include <type_traits>
76480093f4SDimitry Andric
77480093f4SDimitry Andric using namespace lldb;
78480093f4SDimitry Andric using namespace lldb_private;
79480093f4SDimitry Andric using llvm::None;
80480093f4SDimitry Andric using llvm::Optional;
81480093f4SDimitry Andric using llvm::StringRef;
82480093f4SDimitry Andric
83480093f4SDimitry Andric // we may want curses to be disabled for some builds for instance, windows
84480093f4SDimitry Andric #if LLDB_ENABLE_CURSES
85480093f4SDimitry Andric
86480093f4SDimitry Andric #define KEY_RETURN 10
87480093f4SDimitry Andric #define KEY_ESCAPE 27
88480093f4SDimitry Andric
89*5f7ddb14SDimitry Andric #define KEY_SHIFT_TAB (KEY_MAX + 1)
90*5f7ddb14SDimitry Andric
91480093f4SDimitry Andric namespace curses {
92480093f4SDimitry Andric class Menu;
93480093f4SDimitry Andric class MenuDelegate;
94480093f4SDimitry Andric class Window;
95480093f4SDimitry Andric class WindowDelegate;
96480093f4SDimitry Andric typedef std::shared_ptr<Menu> MenuSP;
97480093f4SDimitry Andric typedef std::shared_ptr<MenuDelegate> MenuDelegateSP;
98480093f4SDimitry Andric typedef std::shared_ptr<Window> WindowSP;
99480093f4SDimitry Andric typedef std::shared_ptr<WindowDelegate> WindowDelegateSP;
100480093f4SDimitry Andric typedef std::vector<MenuSP> Menus;
101480093f4SDimitry Andric typedef std::vector<WindowSP> Windows;
102480093f4SDimitry Andric typedef std::vector<WindowDelegateSP> WindowDelegates;
103480093f4SDimitry Andric
104480093f4SDimitry Andric #if 0
105480093f4SDimitry Andric type summary add -s "x=${var.x}, y=${var.y}" curses::Point
106480093f4SDimitry Andric type summary add -s "w=${var.width}, h=${var.height}" curses::Size
107480093f4SDimitry Andric type summary add -s "${var.origin%S} ${var.size%S}" curses::Rect
108480093f4SDimitry Andric #endif
109480093f4SDimitry Andric
110480093f4SDimitry Andric struct Point {
111480093f4SDimitry Andric int x;
112480093f4SDimitry Andric int y;
113480093f4SDimitry Andric
Pointcurses::Point114480093f4SDimitry Andric Point(int _x = 0, int _y = 0) : x(_x), y(_y) {}
115480093f4SDimitry Andric
Clearcurses::Point116480093f4SDimitry Andric void Clear() {
117480093f4SDimitry Andric x = 0;
118480093f4SDimitry Andric y = 0;
119480093f4SDimitry Andric }
120480093f4SDimitry Andric
operator +=curses::Point121480093f4SDimitry Andric Point &operator+=(const Point &rhs) {
122480093f4SDimitry Andric x += rhs.x;
123480093f4SDimitry Andric y += rhs.y;
124480093f4SDimitry Andric return *this;
125480093f4SDimitry Andric }
126480093f4SDimitry Andric
Dumpcurses::Point127480093f4SDimitry Andric void Dump() { printf("(x=%i, y=%i)\n", x, y); }
128480093f4SDimitry Andric };
129480093f4SDimitry Andric
operator ==(const Point & lhs,const Point & rhs)130480093f4SDimitry Andric bool operator==(const Point &lhs, const Point &rhs) {
131480093f4SDimitry Andric return lhs.x == rhs.x && lhs.y == rhs.y;
132480093f4SDimitry Andric }
133480093f4SDimitry Andric
operator !=(const Point & lhs,const Point & rhs)134480093f4SDimitry Andric bool operator!=(const Point &lhs, const Point &rhs) {
135480093f4SDimitry Andric return lhs.x != rhs.x || lhs.y != rhs.y;
136480093f4SDimitry Andric }
137480093f4SDimitry Andric
138480093f4SDimitry Andric struct Size {
139480093f4SDimitry Andric int width;
140480093f4SDimitry Andric int height;
Sizecurses::Size141480093f4SDimitry Andric Size(int w = 0, int h = 0) : width(w), height(h) {}
142480093f4SDimitry Andric
Clearcurses::Size143480093f4SDimitry Andric void Clear() {
144480093f4SDimitry Andric width = 0;
145480093f4SDimitry Andric height = 0;
146480093f4SDimitry Andric }
147480093f4SDimitry Andric
Dumpcurses::Size148480093f4SDimitry Andric void Dump() { printf("(w=%i, h=%i)\n", width, height); }
149480093f4SDimitry Andric };
150480093f4SDimitry Andric
operator ==(const Size & lhs,const Size & rhs)151480093f4SDimitry Andric bool operator==(const Size &lhs, const Size &rhs) {
152480093f4SDimitry Andric return lhs.width == rhs.width && lhs.height == rhs.height;
153480093f4SDimitry Andric }
154480093f4SDimitry Andric
operator !=(const Size & lhs,const Size & rhs)155480093f4SDimitry Andric bool operator!=(const Size &lhs, const Size &rhs) {
156480093f4SDimitry Andric return lhs.width != rhs.width || lhs.height != rhs.height;
157480093f4SDimitry Andric }
158480093f4SDimitry Andric
159480093f4SDimitry Andric struct Rect {
160480093f4SDimitry Andric Point origin;
161480093f4SDimitry Andric Size size;
162480093f4SDimitry Andric
Rectcurses::Rect163480093f4SDimitry Andric Rect() : origin(), size() {}
164480093f4SDimitry Andric
Rectcurses::Rect165480093f4SDimitry Andric Rect(const Point &p, const Size &s) : origin(p), size(s) {}
166480093f4SDimitry Andric
Clearcurses::Rect167480093f4SDimitry Andric void Clear() {
168480093f4SDimitry Andric origin.Clear();
169480093f4SDimitry Andric size.Clear();
170480093f4SDimitry Andric }
171480093f4SDimitry Andric
Dumpcurses::Rect172480093f4SDimitry Andric void Dump() {
173480093f4SDimitry Andric printf("(x=%i, y=%i), w=%i, h=%i)\n", origin.x, origin.y, size.width,
174480093f4SDimitry Andric size.height);
175480093f4SDimitry Andric }
176480093f4SDimitry Andric
Insetcurses::Rect177480093f4SDimitry Andric void Inset(int w, int h) {
178480093f4SDimitry Andric if (size.width > w * 2)
179480093f4SDimitry Andric size.width -= w * 2;
180480093f4SDimitry Andric origin.x += w;
181480093f4SDimitry Andric
182480093f4SDimitry Andric if (size.height > h * 2)
183480093f4SDimitry Andric size.height -= h * 2;
184480093f4SDimitry Andric origin.y += h;
185480093f4SDimitry Andric }
186480093f4SDimitry Andric
187480093f4SDimitry Andric // Return a status bar rectangle which is the last line of this rectangle.
188480093f4SDimitry Andric // This rectangle will be modified to not include the status bar area.
MakeStatusBarcurses::Rect189480093f4SDimitry Andric Rect MakeStatusBar() {
190480093f4SDimitry Andric Rect status_bar;
191480093f4SDimitry Andric if (size.height > 1) {
192480093f4SDimitry Andric status_bar.origin.x = origin.x;
193480093f4SDimitry Andric status_bar.origin.y = size.height;
194480093f4SDimitry Andric status_bar.size.width = size.width;
195480093f4SDimitry Andric status_bar.size.height = 1;
196480093f4SDimitry Andric --size.height;
197480093f4SDimitry Andric }
198480093f4SDimitry Andric return status_bar;
199480093f4SDimitry Andric }
200480093f4SDimitry Andric
201480093f4SDimitry Andric // Return a menubar rectangle which is the first line of this rectangle. This
202480093f4SDimitry Andric // rectangle will be modified to not include the menubar area.
MakeMenuBarcurses::Rect203480093f4SDimitry Andric Rect MakeMenuBar() {
204480093f4SDimitry Andric Rect menubar;
205480093f4SDimitry Andric if (size.height > 1) {
206480093f4SDimitry Andric menubar.origin.x = origin.x;
207480093f4SDimitry Andric menubar.origin.y = origin.y;
208480093f4SDimitry Andric menubar.size.width = size.width;
209480093f4SDimitry Andric menubar.size.height = 1;
210480093f4SDimitry Andric ++origin.y;
211480093f4SDimitry Andric --size.height;
212480093f4SDimitry Andric }
213480093f4SDimitry Andric return menubar;
214480093f4SDimitry Andric }
215480093f4SDimitry Andric
HorizontalSplitPercentagecurses::Rect216480093f4SDimitry Andric void HorizontalSplitPercentage(float top_percentage, Rect &top,
217480093f4SDimitry Andric Rect &bottom) const {
218480093f4SDimitry Andric float top_height = top_percentage * size.height;
219480093f4SDimitry Andric HorizontalSplit(top_height, top, bottom);
220480093f4SDimitry Andric }
221480093f4SDimitry Andric
HorizontalSplitcurses::Rect222480093f4SDimitry Andric void HorizontalSplit(int top_height, Rect &top, Rect &bottom) const {
223480093f4SDimitry Andric top = *this;
224480093f4SDimitry Andric if (top_height < size.height) {
225480093f4SDimitry Andric top.size.height = top_height;
226480093f4SDimitry Andric bottom.origin.x = origin.x;
227480093f4SDimitry Andric bottom.origin.y = origin.y + top.size.height;
228480093f4SDimitry Andric bottom.size.width = size.width;
229480093f4SDimitry Andric bottom.size.height = size.height - top.size.height;
230480093f4SDimitry Andric } else {
231480093f4SDimitry Andric bottom.Clear();
232480093f4SDimitry Andric }
233480093f4SDimitry Andric }
234480093f4SDimitry Andric
VerticalSplitPercentagecurses::Rect235480093f4SDimitry Andric void VerticalSplitPercentage(float left_percentage, Rect &left,
236480093f4SDimitry Andric Rect &right) const {
237480093f4SDimitry Andric float left_width = left_percentage * size.width;
238480093f4SDimitry Andric VerticalSplit(left_width, left, right);
239480093f4SDimitry Andric }
240480093f4SDimitry Andric
VerticalSplitcurses::Rect241480093f4SDimitry Andric void VerticalSplit(int left_width, Rect &left, Rect &right) const {
242480093f4SDimitry Andric left = *this;
243480093f4SDimitry Andric if (left_width < size.width) {
244480093f4SDimitry Andric left.size.width = left_width;
245480093f4SDimitry Andric right.origin.x = origin.x + left.size.width;
246480093f4SDimitry Andric right.origin.y = origin.y;
247480093f4SDimitry Andric right.size.width = size.width - left.size.width;
248480093f4SDimitry Andric right.size.height = size.height;
249480093f4SDimitry Andric } else {
250480093f4SDimitry Andric right.Clear();
251480093f4SDimitry Andric }
252480093f4SDimitry Andric }
253480093f4SDimitry Andric };
254480093f4SDimitry Andric
operator ==(const Rect & lhs,const Rect & rhs)255480093f4SDimitry Andric bool operator==(const Rect &lhs, const Rect &rhs) {
256480093f4SDimitry Andric return lhs.origin == rhs.origin && lhs.size == rhs.size;
257480093f4SDimitry Andric }
258480093f4SDimitry Andric
operator !=(const Rect & lhs,const Rect & rhs)259480093f4SDimitry Andric bool operator!=(const Rect &lhs, const Rect &rhs) {
260480093f4SDimitry Andric return lhs.origin != rhs.origin || lhs.size != rhs.size;
261480093f4SDimitry Andric }
262480093f4SDimitry Andric
263480093f4SDimitry Andric enum HandleCharResult {
264480093f4SDimitry Andric eKeyNotHandled = 0,
265480093f4SDimitry Andric eKeyHandled = 1,
266480093f4SDimitry Andric eQuitApplication = 2
267480093f4SDimitry Andric };
268480093f4SDimitry Andric
269480093f4SDimitry Andric enum class MenuActionResult {
270480093f4SDimitry Andric Handled,
271480093f4SDimitry Andric NotHandled,
272480093f4SDimitry Andric Quit // Exit all menus and quit
273480093f4SDimitry Andric };
274480093f4SDimitry Andric
275480093f4SDimitry Andric struct KeyHelp {
276480093f4SDimitry Andric int ch;
277480093f4SDimitry Andric const char *description;
278480093f4SDimitry Andric };
279480093f4SDimitry Andric
280af732203SDimitry Andric // COLOR_PAIR index names
281af732203SDimitry Andric enum {
282af732203SDimitry Andric // First 16 colors are 8 black background and 8 blue background colors,
283af732203SDimitry Andric // needed by OutputColoredStringTruncated().
284af732203SDimitry Andric BlackOnBlack = 1,
285af732203SDimitry Andric RedOnBlack,
286af732203SDimitry Andric GreenOnBlack,
287af732203SDimitry Andric YellowOnBlack,
288af732203SDimitry Andric BlueOnBlack,
289af732203SDimitry Andric MagentaOnBlack,
290af732203SDimitry Andric CyanOnBlack,
291af732203SDimitry Andric WhiteOnBlack,
292af732203SDimitry Andric BlackOnBlue,
293af732203SDimitry Andric RedOnBlue,
294af732203SDimitry Andric GreenOnBlue,
295af732203SDimitry Andric YellowOnBlue,
296af732203SDimitry Andric BlueOnBlue,
297af732203SDimitry Andric MagentaOnBlue,
298af732203SDimitry Andric CyanOnBlue,
299af732203SDimitry Andric WhiteOnBlue,
300af732203SDimitry Andric // Other colors, as needed.
301af732203SDimitry Andric BlackOnWhite,
302af732203SDimitry Andric MagentaOnWhite,
303af732203SDimitry Andric LastColorPairIndex = MagentaOnWhite
304af732203SDimitry Andric };
305af732203SDimitry Andric
306480093f4SDimitry Andric class WindowDelegate {
307480093f4SDimitry Andric public:
308480093f4SDimitry Andric virtual ~WindowDelegate() = default;
309480093f4SDimitry Andric
WindowDelegateDraw(Window & window,bool force)310480093f4SDimitry Andric virtual bool WindowDelegateDraw(Window &window, bool force) {
311480093f4SDimitry Andric return false; // Drawing not handled
312480093f4SDimitry Andric }
313480093f4SDimitry Andric
WindowDelegateHandleChar(Window & window,int key)314480093f4SDimitry Andric virtual HandleCharResult WindowDelegateHandleChar(Window &window, int key) {
315480093f4SDimitry Andric return eKeyNotHandled;
316480093f4SDimitry Andric }
317480093f4SDimitry Andric
WindowDelegateGetHelpText()318480093f4SDimitry Andric virtual const char *WindowDelegateGetHelpText() { return nullptr; }
319480093f4SDimitry Andric
WindowDelegateGetKeyHelp()320480093f4SDimitry Andric virtual KeyHelp *WindowDelegateGetKeyHelp() { return nullptr; }
321480093f4SDimitry Andric };
322480093f4SDimitry Andric
323480093f4SDimitry Andric class HelpDialogDelegate : public WindowDelegate {
324480093f4SDimitry Andric public:
325480093f4SDimitry Andric HelpDialogDelegate(const char *text, KeyHelp *key_help_array);
326480093f4SDimitry Andric
327480093f4SDimitry Andric ~HelpDialogDelegate() override;
328480093f4SDimitry Andric
329480093f4SDimitry Andric bool WindowDelegateDraw(Window &window, bool force) override;
330480093f4SDimitry Andric
331480093f4SDimitry Andric HandleCharResult WindowDelegateHandleChar(Window &window, int key) override;
332480093f4SDimitry Andric
GetNumLines() const333480093f4SDimitry Andric size_t GetNumLines() const { return m_text.GetSize(); }
334480093f4SDimitry Andric
GetMaxLineLength() const335480093f4SDimitry Andric size_t GetMaxLineLength() const { return m_text.GetMaxStringLength(); }
336480093f4SDimitry Andric
337480093f4SDimitry Andric protected:
338480093f4SDimitry Andric StringList m_text;
339480093f4SDimitry Andric int m_first_visible_line;
340480093f4SDimitry Andric };
341480093f4SDimitry Andric
342*5f7ddb14SDimitry Andric // A surface is an abstraction for something than can be drawn on. The surface
343*5f7ddb14SDimitry Andric // have a width, a height, a cursor position, and a multitude of drawing
344*5f7ddb14SDimitry Andric // operations. This type should be sub-classed to get an actually useful ncurses
345*5f7ddb14SDimitry Andric // object, such as a Window, SubWindow, Pad, or a SubPad.
346*5f7ddb14SDimitry Andric class Surface {
347480093f4SDimitry Andric public:
Surface()348*5f7ddb14SDimitry Andric Surface() : m_window(nullptr) {}
349480093f4SDimitry Andric
get()350*5f7ddb14SDimitry Andric WINDOW *get() { return m_window; }
351*5f7ddb14SDimitry Andric
operator WINDOW*()352*5f7ddb14SDimitry Andric operator WINDOW *() { return m_window; }
353*5f7ddb14SDimitry Andric
354*5f7ddb14SDimitry Andric // Copy a region of the surface to another surface.
CopyToSurface(Surface & target,Point source_origin,Point target_origin,Size size)355*5f7ddb14SDimitry Andric void CopyToSurface(Surface &target, Point source_origin, Point target_origin,
356*5f7ddb14SDimitry Andric Size size) {
357*5f7ddb14SDimitry Andric ::copywin(m_window, target.get(), source_origin.y, source_origin.x,
358*5f7ddb14SDimitry Andric target_origin.y, target_origin.x,
359*5f7ddb14SDimitry Andric target_origin.y + size.height - 1,
360*5f7ddb14SDimitry Andric target_origin.x + size.width - 1, false);
361480093f4SDimitry Andric }
362480093f4SDimitry Andric
GetCursorX() const363*5f7ddb14SDimitry Andric int GetCursorX() const { return getcurx(m_window); }
GetCursorY() const364*5f7ddb14SDimitry Andric int GetCursorY() const { return getcury(m_window); }
MoveCursor(int x,int y)365*5f7ddb14SDimitry Andric void MoveCursor(int x, int y) { ::wmove(m_window, y, x); }
366480093f4SDimitry Andric
AttributeOn(attr_t attr)367480093f4SDimitry Andric void AttributeOn(attr_t attr) { ::wattron(m_window, attr); }
AttributeOff(attr_t attr)368480093f4SDimitry Andric void AttributeOff(attr_t attr) { ::wattroff(m_window, attr); }
369*5f7ddb14SDimitry Andric
GetMaxX() const370af732203SDimitry Andric int GetMaxX() const { return getmaxx(m_window); }
GetMaxY() const371af732203SDimitry Andric int GetMaxY() const { return getmaxy(m_window); }
GetWidth() const372af732203SDimitry Andric int GetWidth() const { return GetMaxX(); }
GetHeight() const373af732203SDimitry Andric int GetHeight() const { return GetMaxY(); }
GetSize() const374*5f7ddb14SDimitry Andric Size GetSize() const { return Size(GetWidth(), GetHeight()); }
375*5f7ddb14SDimitry Andric // Get a zero origin rectangle width the surface size.
GetFrame() const376*5f7ddb14SDimitry Andric Rect GetFrame() const { return Rect(Point(), GetSize()); }
377*5f7ddb14SDimitry Andric
Clear()378*5f7ddb14SDimitry Andric void Clear() { ::wclear(m_window); }
Erase()379*5f7ddb14SDimitry Andric void Erase() { ::werase(m_window); }
380*5f7ddb14SDimitry Andric
SetBackground(int color_pair_idx)381480093f4SDimitry Andric void SetBackground(int color_pair_idx) {
382480093f4SDimitry Andric ::wbkgd(m_window, COLOR_PAIR(color_pair_idx));
383480093f4SDimitry Andric }
384480093f4SDimitry Andric
PutChar(int ch)385*5f7ddb14SDimitry Andric void PutChar(int ch) { ::waddch(m_window, ch); }
PutCString(const char * s,int len=-1)386*5f7ddb14SDimitry Andric void PutCString(const char *s, int len = -1) { ::waddnstr(m_window, s, len); }
387*5f7ddb14SDimitry Andric
PutCStringTruncated(int right_pad,const char * s,int len=-1)388af732203SDimitry Andric void PutCStringTruncated(int right_pad, const char *s, int len = -1) {
389480093f4SDimitry Andric int bytes_left = GetWidth() - GetCursorX();
390480093f4SDimitry Andric if (bytes_left > right_pad) {
391480093f4SDimitry Andric bytes_left -= right_pad;
392af732203SDimitry Andric ::waddnstr(m_window, s, len < 0 ? bytes_left : std::min(bytes_left, len));
393480093f4SDimitry Andric }
394480093f4SDimitry Andric }
395480093f4SDimitry Andric
Printf(const char * format,...)396480093f4SDimitry Andric void Printf(const char *format, ...) __attribute__((format(printf, 2, 3))) {
397480093f4SDimitry Andric va_list args;
398480093f4SDimitry Andric va_start(args, format);
399*5f7ddb14SDimitry Andric vw_printw(m_window, format, args);
400480093f4SDimitry Andric va_end(args);
401480093f4SDimitry Andric }
402480093f4SDimitry Andric
PrintfTruncated(int right_pad,const char * format,...)403af732203SDimitry Andric void PrintfTruncated(int right_pad, const char *format, ...)
404af732203SDimitry Andric __attribute__((format(printf, 3, 4))) {
405af732203SDimitry Andric va_list args;
406af732203SDimitry Andric va_start(args, format);
407af732203SDimitry Andric StreamString strm;
408af732203SDimitry Andric strm.PrintfVarArg(format, args);
409af732203SDimitry Andric va_end(args);
410af732203SDimitry Andric PutCStringTruncated(right_pad, strm.GetData());
411af732203SDimitry Andric }
412af732203SDimitry Andric
VerticalLine(int n,chtype v_char=ACS_VLINE)413*5f7ddb14SDimitry Andric void VerticalLine(int n, chtype v_char = ACS_VLINE) {
414*5f7ddb14SDimitry Andric ::wvline(m_window, v_char, n);
415*5f7ddb14SDimitry Andric }
HorizontalLine(int n,chtype h_char=ACS_HLINE)416*5f7ddb14SDimitry Andric void HorizontalLine(int n, chtype h_char = ACS_HLINE) {
417*5f7ddb14SDimitry Andric ::whline(m_window, h_char, n);
418*5f7ddb14SDimitry Andric }
Box(chtype v_char=ACS_VLINE,chtype h_char=ACS_HLINE)419*5f7ddb14SDimitry Andric void Box(chtype v_char = ACS_VLINE, chtype h_char = ACS_HLINE) {
420*5f7ddb14SDimitry Andric ::box(m_window, v_char, h_char);
421*5f7ddb14SDimitry Andric }
422*5f7ddb14SDimitry Andric
TitledBox(const char * title,chtype v_char=ACS_VLINE,chtype h_char=ACS_HLINE)423*5f7ddb14SDimitry Andric void TitledBox(const char *title, chtype v_char = ACS_VLINE,
424*5f7ddb14SDimitry Andric chtype h_char = ACS_HLINE) {
425*5f7ddb14SDimitry Andric Box(v_char, h_char);
426*5f7ddb14SDimitry Andric int title_offset = 2;
427*5f7ddb14SDimitry Andric MoveCursor(title_offset, 0);
428*5f7ddb14SDimitry Andric PutChar('[');
429*5f7ddb14SDimitry Andric PutCString(title, GetWidth() - title_offset);
430*5f7ddb14SDimitry Andric PutChar(']');
431*5f7ddb14SDimitry Andric }
432*5f7ddb14SDimitry Andric
Box(const Rect & bounds,chtype v_char=ACS_VLINE,chtype h_char=ACS_HLINE)433*5f7ddb14SDimitry Andric void Box(const Rect &bounds, chtype v_char = ACS_VLINE,
434*5f7ddb14SDimitry Andric chtype h_char = ACS_HLINE) {
435*5f7ddb14SDimitry Andric MoveCursor(bounds.origin.x, bounds.origin.y);
436*5f7ddb14SDimitry Andric VerticalLine(bounds.size.height);
437*5f7ddb14SDimitry Andric HorizontalLine(bounds.size.width);
438*5f7ddb14SDimitry Andric PutChar(ACS_ULCORNER);
439*5f7ddb14SDimitry Andric
440*5f7ddb14SDimitry Andric MoveCursor(bounds.origin.x + bounds.size.width - 1, bounds.origin.y);
441*5f7ddb14SDimitry Andric VerticalLine(bounds.size.height);
442*5f7ddb14SDimitry Andric PutChar(ACS_URCORNER);
443*5f7ddb14SDimitry Andric
444*5f7ddb14SDimitry Andric MoveCursor(bounds.origin.x, bounds.origin.y + bounds.size.height - 1);
445*5f7ddb14SDimitry Andric HorizontalLine(bounds.size.width);
446*5f7ddb14SDimitry Andric PutChar(ACS_LLCORNER);
447*5f7ddb14SDimitry Andric
448*5f7ddb14SDimitry Andric MoveCursor(bounds.origin.x + bounds.size.width - 1,
449*5f7ddb14SDimitry Andric bounds.origin.y + bounds.size.height - 1);
450*5f7ddb14SDimitry Andric PutChar(ACS_LRCORNER);
451*5f7ddb14SDimitry Andric }
452*5f7ddb14SDimitry Andric
TitledBox(const Rect & bounds,const char * title,chtype v_char=ACS_VLINE,chtype h_char=ACS_HLINE)453*5f7ddb14SDimitry Andric void TitledBox(const Rect &bounds, const char *title,
454*5f7ddb14SDimitry Andric chtype v_char = ACS_VLINE, chtype h_char = ACS_HLINE) {
455*5f7ddb14SDimitry Andric Box(bounds, v_char, h_char);
456*5f7ddb14SDimitry Andric int title_offset = 2;
457*5f7ddb14SDimitry Andric MoveCursor(bounds.origin.x + title_offset, bounds.origin.y);
458*5f7ddb14SDimitry Andric PutChar('[');
459*5f7ddb14SDimitry Andric PutCString(title, bounds.size.width - title_offset);
460*5f7ddb14SDimitry Andric PutChar(']');
461af732203SDimitry Andric }
462af732203SDimitry Andric
463af732203SDimitry Andric // Curses doesn't allow direct output of color escape sequences, but that's
464af732203SDimitry Andric // how we get source lines from the Highligher class. Read the line and
465af732203SDimitry Andric // convert color escape sequences to curses color attributes. Use
466af732203SDimitry Andric // first_skip_count to skip leading visible characters. Returns false if all
467af732203SDimitry Andric // visible characters were skipped due to first_skip_count.
OutputColoredStringTruncated(int right_pad,StringRef string,size_t skip_first_count,bool use_blue_background)468af732203SDimitry Andric bool OutputColoredStringTruncated(int right_pad, StringRef string,
469af732203SDimitry Andric size_t skip_first_count,
470af732203SDimitry Andric bool use_blue_background) {
471af732203SDimitry Andric attr_t saved_attr;
472af732203SDimitry Andric short saved_pair;
473af732203SDimitry Andric bool result = false;
474af732203SDimitry Andric wattr_get(m_window, &saved_attr, &saved_pair, nullptr);
475af732203SDimitry Andric if (use_blue_background)
476af732203SDimitry Andric ::wattron(m_window, COLOR_PAIR(WhiteOnBlue));
477af732203SDimitry Andric while (!string.empty()) {
478af732203SDimitry Andric size_t esc_pos = string.find('\x1b');
479af732203SDimitry Andric if (esc_pos == StringRef::npos) {
480af732203SDimitry Andric string = string.substr(skip_first_count);
481af732203SDimitry Andric if (!string.empty()) {
482af732203SDimitry Andric PutCStringTruncated(right_pad, string.data(), string.size());
483af732203SDimitry Andric result = true;
484af732203SDimitry Andric }
485af732203SDimitry Andric break;
486af732203SDimitry Andric }
487af732203SDimitry Andric if (esc_pos > 0) {
488af732203SDimitry Andric if (skip_first_count > 0) {
489af732203SDimitry Andric int skip = std::min(esc_pos, skip_first_count);
490af732203SDimitry Andric string = string.substr(skip);
491af732203SDimitry Andric skip_first_count -= skip;
492af732203SDimitry Andric esc_pos -= skip;
493af732203SDimitry Andric }
494af732203SDimitry Andric if (esc_pos > 0) {
495af732203SDimitry Andric PutCStringTruncated(right_pad, string.data(), esc_pos);
496af732203SDimitry Andric result = true;
497af732203SDimitry Andric string = string.drop_front(esc_pos);
498af732203SDimitry Andric }
499af732203SDimitry Andric }
500af732203SDimitry Andric bool consumed = string.consume_front("\x1b");
501af732203SDimitry Andric assert(consumed);
502af732203SDimitry Andric UNUSED_IF_ASSERT_DISABLED(consumed);
503af732203SDimitry Andric // This is written to match our Highlighter classes, which seem to
504af732203SDimitry Andric // generate only foreground color escape sequences. If necessary, this
505af732203SDimitry Andric // will need to be extended.
506af732203SDimitry Andric if (!string.consume_front("[")) {
507af732203SDimitry Andric llvm::errs() << "Missing '[' in color escape sequence.\n";
508af732203SDimitry Andric continue;
509af732203SDimitry Andric }
510af732203SDimitry Andric // Only 8 basic foreground colors and reset, our Highlighter doesn't use
511af732203SDimitry Andric // anything else.
512af732203SDimitry Andric int value;
513af732203SDimitry Andric if (!!string.consumeInteger(10, value) || // Returns false on success.
514af732203SDimitry Andric !(value == 0 || (value >= 30 && value <= 37))) {
515af732203SDimitry Andric llvm::errs() << "No valid color code in color escape sequence.\n";
516af732203SDimitry Andric continue;
517af732203SDimitry Andric }
518af732203SDimitry Andric if (!string.consume_front("m")) {
519af732203SDimitry Andric llvm::errs() << "Missing 'm' in color escape sequence.\n";
520af732203SDimitry Andric continue;
521af732203SDimitry Andric }
522af732203SDimitry Andric if (value == 0) { // Reset.
523af732203SDimitry Andric wattr_set(m_window, saved_attr, saved_pair, nullptr);
524af732203SDimitry Andric if (use_blue_background)
525af732203SDimitry Andric ::wattron(m_window, COLOR_PAIR(WhiteOnBlue));
526af732203SDimitry Andric } else {
527af732203SDimitry Andric // Mapped directly to first 16 color pairs (black/blue background).
528af732203SDimitry Andric ::wattron(m_window,
529af732203SDimitry Andric COLOR_PAIR(value - 30 + 1 + (use_blue_background ? 8 : 0)));
530af732203SDimitry Andric }
531af732203SDimitry Andric }
532af732203SDimitry Andric wattr_set(m_window, saved_attr, saved_pair, nullptr);
533af732203SDimitry Andric return result;
534af732203SDimitry Andric }
535af732203SDimitry Andric
536*5f7ddb14SDimitry Andric protected:
537*5f7ddb14SDimitry Andric WINDOW *m_window;
538*5f7ddb14SDimitry Andric };
539*5f7ddb14SDimitry Andric
540*5f7ddb14SDimitry Andric class Pad : public Surface {
541*5f7ddb14SDimitry Andric public:
Pad(Size size)542*5f7ddb14SDimitry Andric Pad(Size size) { m_window = ::newpad(size.height, size.width); }
543*5f7ddb14SDimitry Andric
~Pad()544*5f7ddb14SDimitry Andric ~Pad() { ::delwin(m_window); }
545*5f7ddb14SDimitry Andric };
546*5f7ddb14SDimitry Andric
547*5f7ddb14SDimitry Andric class SubPad : public Surface {
548*5f7ddb14SDimitry Andric public:
SubPad(Pad & pad,Rect bounds)549*5f7ddb14SDimitry Andric SubPad(Pad &pad, Rect bounds) {
550*5f7ddb14SDimitry Andric m_window = ::subpad(pad.get(), bounds.size.height, bounds.size.width,
551*5f7ddb14SDimitry Andric bounds.origin.y, bounds.origin.x);
552*5f7ddb14SDimitry Andric }
SubPad(SubPad & subpad,Rect bounds)553*5f7ddb14SDimitry Andric SubPad(SubPad &subpad, Rect bounds) {
554*5f7ddb14SDimitry Andric m_window = ::subpad(subpad.get(), bounds.size.height, bounds.size.width,
555*5f7ddb14SDimitry Andric bounds.origin.y, bounds.origin.x);
556*5f7ddb14SDimitry Andric }
557*5f7ddb14SDimitry Andric
~SubPad()558*5f7ddb14SDimitry Andric ~SubPad() { ::delwin(m_window); }
559*5f7ddb14SDimitry Andric };
560*5f7ddb14SDimitry Andric
561*5f7ddb14SDimitry Andric class Window : public Surface {
562*5f7ddb14SDimitry Andric public:
Window(const char * name)563*5f7ddb14SDimitry Andric Window(const char *name)
564*5f7ddb14SDimitry Andric : m_name(name), m_panel(nullptr), m_parent(nullptr), m_subwindows(),
565*5f7ddb14SDimitry Andric m_delegate_sp(), m_curr_active_window_idx(UINT32_MAX),
566*5f7ddb14SDimitry Andric m_prev_active_window_idx(UINT32_MAX), m_delete(false),
567*5f7ddb14SDimitry Andric m_needs_update(true), m_can_activate(true), m_is_subwin(false) {}
568*5f7ddb14SDimitry Andric
Window(const char * name,WINDOW * w,bool del=true)569*5f7ddb14SDimitry Andric Window(const char *name, WINDOW *w, bool del = true)
570*5f7ddb14SDimitry Andric : m_name(name), m_panel(nullptr), m_parent(nullptr), m_subwindows(),
571*5f7ddb14SDimitry Andric m_delegate_sp(), m_curr_active_window_idx(UINT32_MAX),
572*5f7ddb14SDimitry Andric m_prev_active_window_idx(UINT32_MAX), m_delete(del),
573*5f7ddb14SDimitry Andric m_needs_update(true), m_can_activate(true), m_is_subwin(false) {
574*5f7ddb14SDimitry Andric if (w)
575*5f7ddb14SDimitry Andric Reset(w);
576*5f7ddb14SDimitry Andric }
577*5f7ddb14SDimitry Andric
Window(const char * name,const Rect & bounds)578*5f7ddb14SDimitry Andric Window(const char *name, const Rect &bounds)
579*5f7ddb14SDimitry Andric : m_name(name), m_parent(nullptr), m_subwindows(), m_delegate_sp(),
580*5f7ddb14SDimitry Andric m_curr_active_window_idx(UINT32_MAX),
581*5f7ddb14SDimitry Andric m_prev_active_window_idx(UINT32_MAX), m_delete(true),
582*5f7ddb14SDimitry Andric m_needs_update(true), m_can_activate(true), m_is_subwin(false) {
583*5f7ddb14SDimitry Andric Reset(::newwin(bounds.size.height, bounds.size.width, bounds.origin.y,
584*5f7ddb14SDimitry Andric bounds.origin.y));
585*5f7ddb14SDimitry Andric }
586*5f7ddb14SDimitry Andric
~Window()587*5f7ddb14SDimitry Andric virtual ~Window() {
588*5f7ddb14SDimitry Andric RemoveSubWindows();
589*5f7ddb14SDimitry Andric Reset();
590*5f7ddb14SDimitry Andric }
591*5f7ddb14SDimitry Andric
Reset(WINDOW * w=nullptr,bool del=true)592*5f7ddb14SDimitry Andric void Reset(WINDOW *w = nullptr, bool del = true) {
593*5f7ddb14SDimitry Andric if (m_window == w)
594*5f7ddb14SDimitry Andric return;
595*5f7ddb14SDimitry Andric
596*5f7ddb14SDimitry Andric if (m_panel) {
597*5f7ddb14SDimitry Andric ::del_panel(m_panel);
598*5f7ddb14SDimitry Andric m_panel = nullptr;
599*5f7ddb14SDimitry Andric }
600*5f7ddb14SDimitry Andric if (m_window && m_delete) {
601*5f7ddb14SDimitry Andric ::delwin(m_window);
602*5f7ddb14SDimitry Andric m_window = nullptr;
603*5f7ddb14SDimitry Andric m_delete = false;
604*5f7ddb14SDimitry Andric }
605*5f7ddb14SDimitry Andric if (w) {
606*5f7ddb14SDimitry Andric m_window = w;
607*5f7ddb14SDimitry Andric m_panel = ::new_panel(m_window);
608*5f7ddb14SDimitry Andric m_delete = del;
609*5f7ddb14SDimitry Andric }
610*5f7ddb14SDimitry Andric }
611*5f7ddb14SDimitry Andric
612*5f7ddb14SDimitry Andric // Get the rectangle in our parent window
GetBounds() const613*5f7ddb14SDimitry Andric Rect GetBounds() const { return Rect(GetParentOrigin(), GetSize()); }
614*5f7ddb14SDimitry Andric
GetCenteredRect(int width,int height)615*5f7ddb14SDimitry Andric Rect GetCenteredRect(int width, int height) {
616*5f7ddb14SDimitry Andric Size size = GetSize();
617*5f7ddb14SDimitry Andric width = std::min(size.width, width);
618*5f7ddb14SDimitry Andric height = std::min(size.height, height);
619*5f7ddb14SDimitry Andric int x = (size.width - width) / 2;
620*5f7ddb14SDimitry Andric int y = (size.height - height) / 2;
621*5f7ddb14SDimitry Andric return Rect(Point(x, y), Size(width, height));
622*5f7ddb14SDimitry Andric }
623*5f7ddb14SDimitry Andric
GetChar()624*5f7ddb14SDimitry Andric int GetChar() { return ::wgetch(m_window); }
GetParentOrigin() const625*5f7ddb14SDimitry Andric Point GetParentOrigin() const { return Point(GetParentX(), GetParentY()); }
GetParentX() const626*5f7ddb14SDimitry Andric int GetParentX() const { return getparx(m_window); }
GetParentY() const627*5f7ddb14SDimitry Andric int GetParentY() const { return getpary(m_window); }
MoveWindow(int x,int y)628*5f7ddb14SDimitry Andric void MoveWindow(int x, int y) { MoveWindow(Point(x, y)); }
Resize(int w,int h)629*5f7ddb14SDimitry Andric void Resize(int w, int h) { ::wresize(m_window, h, w); }
Resize(const Size & size)630*5f7ddb14SDimitry Andric void Resize(const Size &size) {
631*5f7ddb14SDimitry Andric ::wresize(m_window, size.height, size.width);
632*5f7ddb14SDimitry Andric }
MoveWindow(const Point & origin)633*5f7ddb14SDimitry Andric void MoveWindow(const Point &origin) {
634*5f7ddb14SDimitry Andric const bool moving_window = origin != GetParentOrigin();
635*5f7ddb14SDimitry Andric if (m_is_subwin && moving_window) {
636*5f7ddb14SDimitry Andric // Can't move subwindows, must delete and re-create
637*5f7ddb14SDimitry Andric Size size = GetSize();
638*5f7ddb14SDimitry Andric Reset(::subwin(m_parent->m_window, size.height, size.width, origin.y,
639*5f7ddb14SDimitry Andric origin.x),
640*5f7ddb14SDimitry Andric true);
641*5f7ddb14SDimitry Andric } else {
642*5f7ddb14SDimitry Andric ::mvwin(m_window, origin.y, origin.x);
643*5f7ddb14SDimitry Andric }
644*5f7ddb14SDimitry Andric }
645*5f7ddb14SDimitry Andric
SetBounds(const Rect & bounds)646*5f7ddb14SDimitry Andric void SetBounds(const Rect &bounds) {
647*5f7ddb14SDimitry Andric const bool moving_window = bounds.origin != GetParentOrigin();
648*5f7ddb14SDimitry Andric if (m_is_subwin && moving_window) {
649*5f7ddb14SDimitry Andric // Can't move subwindows, must delete and re-create
650*5f7ddb14SDimitry Andric Reset(::subwin(m_parent->m_window, bounds.size.height, bounds.size.width,
651*5f7ddb14SDimitry Andric bounds.origin.y, bounds.origin.x),
652*5f7ddb14SDimitry Andric true);
653*5f7ddb14SDimitry Andric } else {
654*5f7ddb14SDimitry Andric if (moving_window)
655*5f7ddb14SDimitry Andric MoveWindow(bounds.origin);
656*5f7ddb14SDimitry Andric Resize(bounds.size);
657*5f7ddb14SDimitry Andric }
658*5f7ddb14SDimitry Andric }
659*5f7ddb14SDimitry Andric
Touch()660480093f4SDimitry Andric void Touch() {
661480093f4SDimitry Andric ::touchwin(m_window);
662480093f4SDimitry Andric if (m_parent)
663480093f4SDimitry Andric m_parent->Touch();
664480093f4SDimitry Andric }
665480093f4SDimitry Andric
CreateSubWindow(const char * name,const Rect & bounds,bool make_active)666480093f4SDimitry Andric WindowSP CreateSubWindow(const char *name, const Rect &bounds,
667480093f4SDimitry Andric bool make_active) {
668480093f4SDimitry Andric auto get_window = [this, &bounds]() {
669480093f4SDimitry Andric return m_window
670480093f4SDimitry Andric ? ::subwin(m_window, bounds.size.height, bounds.size.width,
671480093f4SDimitry Andric bounds.origin.y, bounds.origin.x)
672480093f4SDimitry Andric : ::newwin(bounds.size.height, bounds.size.width,
673480093f4SDimitry Andric bounds.origin.y, bounds.origin.x);
674480093f4SDimitry Andric };
675480093f4SDimitry Andric WindowSP subwindow_sp = std::make_shared<Window>(name, get_window(), true);
676480093f4SDimitry Andric subwindow_sp->m_is_subwin = subwindow_sp.operator bool();
677480093f4SDimitry Andric subwindow_sp->m_parent = this;
678480093f4SDimitry Andric if (make_active) {
679480093f4SDimitry Andric m_prev_active_window_idx = m_curr_active_window_idx;
680480093f4SDimitry Andric m_curr_active_window_idx = m_subwindows.size();
681480093f4SDimitry Andric }
682480093f4SDimitry Andric m_subwindows.push_back(subwindow_sp);
683480093f4SDimitry Andric ::top_panel(subwindow_sp->m_panel);
684480093f4SDimitry Andric m_needs_update = true;
685480093f4SDimitry Andric return subwindow_sp;
686480093f4SDimitry Andric }
687480093f4SDimitry Andric
RemoveSubWindow(Window * window)688480093f4SDimitry Andric bool RemoveSubWindow(Window *window) {
689480093f4SDimitry Andric Windows::iterator pos, end = m_subwindows.end();
690480093f4SDimitry Andric size_t i = 0;
691480093f4SDimitry Andric for (pos = m_subwindows.begin(); pos != end; ++pos, ++i) {
692480093f4SDimitry Andric if ((*pos).get() == window) {
693480093f4SDimitry Andric if (m_prev_active_window_idx == i)
694480093f4SDimitry Andric m_prev_active_window_idx = UINT32_MAX;
695480093f4SDimitry Andric else if (m_prev_active_window_idx != UINT32_MAX &&
696480093f4SDimitry Andric m_prev_active_window_idx > i)
697480093f4SDimitry Andric --m_prev_active_window_idx;
698480093f4SDimitry Andric
699480093f4SDimitry Andric if (m_curr_active_window_idx == i)
700480093f4SDimitry Andric m_curr_active_window_idx = UINT32_MAX;
701480093f4SDimitry Andric else if (m_curr_active_window_idx != UINT32_MAX &&
702480093f4SDimitry Andric m_curr_active_window_idx > i)
703480093f4SDimitry Andric --m_curr_active_window_idx;
704480093f4SDimitry Andric window->Erase();
705480093f4SDimitry Andric m_subwindows.erase(pos);
706480093f4SDimitry Andric m_needs_update = true;
707480093f4SDimitry Andric if (m_parent)
708480093f4SDimitry Andric m_parent->Touch();
709480093f4SDimitry Andric else
710480093f4SDimitry Andric ::touchwin(stdscr);
711480093f4SDimitry Andric return true;
712480093f4SDimitry Andric }
713480093f4SDimitry Andric }
714480093f4SDimitry Andric return false;
715480093f4SDimitry Andric }
716480093f4SDimitry Andric
FindSubWindow(const char * name)717480093f4SDimitry Andric WindowSP FindSubWindow(const char *name) {
718480093f4SDimitry Andric Windows::iterator pos, end = m_subwindows.end();
719480093f4SDimitry Andric size_t i = 0;
720480093f4SDimitry Andric for (pos = m_subwindows.begin(); pos != end; ++pos, ++i) {
721480093f4SDimitry Andric if ((*pos)->m_name == name)
722480093f4SDimitry Andric return *pos;
723480093f4SDimitry Andric }
724480093f4SDimitry Andric return WindowSP();
725480093f4SDimitry Andric }
726480093f4SDimitry Andric
RemoveSubWindows()727480093f4SDimitry Andric void RemoveSubWindows() {
728480093f4SDimitry Andric m_curr_active_window_idx = UINT32_MAX;
729480093f4SDimitry Andric m_prev_active_window_idx = UINT32_MAX;
730480093f4SDimitry Andric for (Windows::iterator pos = m_subwindows.begin();
731480093f4SDimitry Andric pos != m_subwindows.end(); pos = m_subwindows.erase(pos)) {
732480093f4SDimitry Andric (*pos)->Erase();
733480093f4SDimitry Andric }
734480093f4SDimitry Andric if (m_parent)
735480093f4SDimitry Andric m_parent->Touch();
736480093f4SDimitry Andric else
737480093f4SDimitry Andric ::touchwin(stdscr);
738480093f4SDimitry Andric }
739480093f4SDimitry Andric
740480093f4SDimitry Andric // Window drawing utilities
DrawTitleBox(const char * title,const char * bottom_message=nullptr)741480093f4SDimitry Andric void DrawTitleBox(const char *title, const char *bottom_message = nullptr) {
742480093f4SDimitry Andric attr_t attr = 0;
743480093f4SDimitry Andric if (IsActive())
744af732203SDimitry Andric attr = A_BOLD | COLOR_PAIR(BlackOnWhite);
745480093f4SDimitry Andric else
746480093f4SDimitry Andric attr = 0;
747480093f4SDimitry Andric if (attr)
748480093f4SDimitry Andric AttributeOn(attr);
749480093f4SDimitry Andric
750480093f4SDimitry Andric Box();
751480093f4SDimitry Andric MoveCursor(3, 0);
752480093f4SDimitry Andric
753480093f4SDimitry Andric if (title && title[0]) {
754480093f4SDimitry Andric PutChar('<');
755480093f4SDimitry Andric PutCString(title);
756480093f4SDimitry Andric PutChar('>');
757480093f4SDimitry Andric }
758480093f4SDimitry Andric
759480093f4SDimitry Andric if (bottom_message && bottom_message[0]) {
760480093f4SDimitry Andric int bottom_message_length = strlen(bottom_message);
761480093f4SDimitry Andric int x = GetWidth() - 3 - (bottom_message_length + 2);
762480093f4SDimitry Andric
763480093f4SDimitry Andric if (x > 0) {
764480093f4SDimitry Andric MoveCursor(x, GetHeight() - 1);
765480093f4SDimitry Andric PutChar('[');
766480093f4SDimitry Andric PutCString(bottom_message);
767480093f4SDimitry Andric PutChar(']');
768480093f4SDimitry Andric } else {
769480093f4SDimitry Andric MoveCursor(1, GetHeight() - 1);
770480093f4SDimitry Andric PutChar('[');
771af732203SDimitry Andric PutCStringTruncated(1, bottom_message);
772480093f4SDimitry Andric }
773480093f4SDimitry Andric }
774480093f4SDimitry Andric if (attr)
775480093f4SDimitry Andric AttributeOff(attr);
776480093f4SDimitry Andric }
777480093f4SDimitry Andric
Draw(bool force)778480093f4SDimitry Andric virtual void Draw(bool force) {
779480093f4SDimitry Andric if (m_delegate_sp && m_delegate_sp->WindowDelegateDraw(*this, force))
780480093f4SDimitry Andric return;
781480093f4SDimitry Andric
782480093f4SDimitry Andric for (auto &subwindow_sp : m_subwindows)
783480093f4SDimitry Andric subwindow_sp->Draw(force);
784480093f4SDimitry Andric }
785480093f4SDimitry Andric
CreateHelpSubwindow()786480093f4SDimitry Andric bool CreateHelpSubwindow() {
787480093f4SDimitry Andric if (m_delegate_sp) {
788480093f4SDimitry Andric const char *text = m_delegate_sp->WindowDelegateGetHelpText();
789480093f4SDimitry Andric KeyHelp *key_help = m_delegate_sp->WindowDelegateGetKeyHelp();
790480093f4SDimitry Andric if ((text && text[0]) || key_help) {
791480093f4SDimitry Andric std::unique_ptr<HelpDialogDelegate> help_delegate_up(
792480093f4SDimitry Andric new HelpDialogDelegate(text, key_help));
793480093f4SDimitry Andric const size_t num_lines = help_delegate_up->GetNumLines();
794480093f4SDimitry Andric const size_t max_length = help_delegate_up->GetMaxLineLength();
795480093f4SDimitry Andric Rect bounds = GetBounds();
796480093f4SDimitry Andric bounds.Inset(1, 1);
797480093f4SDimitry Andric if (max_length + 4 < static_cast<size_t>(bounds.size.width)) {
798480093f4SDimitry Andric bounds.origin.x += (bounds.size.width - max_length + 4) / 2;
799480093f4SDimitry Andric bounds.size.width = max_length + 4;
800480093f4SDimitry Andric } else {
801480093f4SDimitry Andric if (bounds.size.width > 100) {
802480093f4SDimitry Andric const int inset_w = bounds.size.width / 4;
803480093f4SDimitry Andric bounds.origin.x += inset_w;
804480093f4SDimitry Andric bounds.size.width -= 2 * inset_w;
805480093f4SDimitry Andric }
806480093f4SDimitry Andric }
807480093f4SDimitry Andric
808480093f4SDimitry Andric if (num_lines + 2 < static_cast<size_t>(bounds.size.height)) {
809480093f4SDimitry Andric bounds.origin.y += (bounds.size.height - num_lines + 2) / 2;
810480093f4SDimitry Andric bounds.size.height = num_lines + 2;
811480093f4SDimitry Andric } else {
812480093f4SDimitry Andric if (bounds.size.height > 100) {
813480093f4SDimitry Andric const int inset_h = bounds.size.height / 4;
814480093f4SDimitry Andric bounds.origin.y += inset_h;
815480093f4SDimitry Andric bounds.size.height -= 2 * inset_h;
816480093f4SDimitry Andric }
817480093f4SDimitry Andric }
818480093f4SDimitry Andric WindowSP help_window_sp;
819480093f4SDimitry Andric Window *parent_window = GetParent();
820480093f4SDimitry Andric if (parent_window)
821480093f4SDimitry Andric help_window_sp = parent_window->CreateSubWindow("Help", bounds, true);
822480093f4SDimitry Andric else
823480093f4SDimitry Andric help_window_sp = CreateSubWindow("Help", bounds, true);
824480093f4SDimitry Andric help_window_sp->SetDelegate(
825480093f4SDimitry Andric WindowDelegateSP(help_delegate_up.release()));
826480093f4SDimitry Andric return true;
827480093f4SDimitry Andric }
828480093f4SDimitry Andric }
829480093f4SDimitry Andric return false;
830480093f4SDimitry Andric }
831480093f4SDimitry Andric
HandleChar(int key)832480093f4SDimitry Andric virtual HandleCharResult HandleChar(int key) {
833480093f4SDimitry Andric // Always check the active window first
834480093f4SDimitry Andric HandleCharResult result = eKeyNotHandled;
835480093f4SDimitry Andric WindowSP active_window_sp = GetActiveWindow();
836480093f4SDimitry Andric if (active_window_sp) {
837480093f4SDimitry Andric result = active_window_sp->HandleChar(key);
838480093f4SDimitry Andric if (result != eKeyNotHandled)
839480093f4SDimitry Andric return result;
840480093f4SDimitry Andric }
841480093f4SDimitry Andric
842480093f4SDimitry Andric if (m_delegate_sp) {
843480093f4SDimitry Andric result = m_delegate_sp->WindowDelegateHandleChar(*this, key);
844480093f4SDimitry Andric if (result != eKeyNotHandled)
845480093f4SDimitry Andric return result;
846480093f4SDimitry Andric }
847480093f4SDimitry Andric
848480093f4SDimitry Andric // Then check for any windows that want any keys that weren't handled. This
849480093f4SDimitry Andric // is typically only for a menubar. Make a copy of the subwindows in case
850480093f4SDimitry Andric // any HandleChar() functions muck with the subwindows. If we don't do
851480093f4SDimitry Andric // this, we can crash when iterating over the subwindows.
852480093f4SDimitry Andric Windows subwindows(m_subwindows);
853480093f4SDimitry Andric for (auto subwindow_sp : subwindows) {
854480093f4SDimitry Andric if (!subwindow_sp->m_can_activate) {
855480093f4SDimitry Andric HandleCharResult result = subwindow_sp->HandleChar(key);
856480093f4SDimitry Andric if (result != eKeyNotHandled)
857480093f4SDimitry Andric return result;
858480093f4SDimitry Andric }
859480093f4SDimitry Andric }
860480093f4SDimitry Andric
861480093f4SDimitry Andric return eKeyNotHandled;
862480093f4SDimitry Andric }
863480093f4SDimitry Andric
GetActiveWindow()864480093f4SDimitry Andric WindowSP GetActiveWindow() {
865480093f4SDimitry Andric if (!m_subwindows.empty()) {
866480093f4SDimitry Andric if (m_curr_active_window_idx >= m_subwindows.size()) {
867480093f4SDimitry Andric if (m_prev_active_window_idx < m_subwindows.size()) {
868480093f4SDimitry Andric m_curr_active_window_idx = m_prev_active_window_idx;
869480093f4SDimitry Andric m_prev_active_window_idx = UINT32_MAX;
870480093f4SDimitry Andric } else if (IsActive()) {
871480093f4SDimitry Andric m_prev_active_window_idx = UINT32_MAX;
872480093f4SDimitry Andric m_curr_active_window_idx = UINT32_MAX;
873480093f4SDimitry Andric
874480093f4SDimitry Andric // Find first window that wants to be active if this window is active
875480093f4SDimitry Andric const size_t num_subwindows = m_subwindows.size();
876480093f4SDimitry Andric for (size_t i = 0; i < num_subwindows; ++i) {
877480093f4SDimitry Andric if (m_subwindows[i]->GetCanBeActive()) {
878480093f4SDimitry Andric m_curr_active_window_idx = i;
879480093f4SDimitry Andric break;
880480093f4SDimitry Andric }
881480093f4SDimitry Andric }
882480093f4SDimitry Andric }
883480093f4SDimitry Andric }
884480093f4SDimitry Andric
885480093f4SDimitry Andric if (m_curr_active_window_idx < m_subwindows.size())
886480093f4SDimitry Andric return m_subwindows[m_curr_active_window_idx];
887480093f4SDimitry Andric }
888480093f4SDimitry Andric return WindowSP();
889480093f4SDimitry Andric }
890480093f4SDimitry Andric
GetCanBeActive() const891480093f4SDimitry Andric bool GetCanBeActive() const { return m_can_activate; }
892480093f4SDimitry Andric
SetCanBeActive(bool b)893480093f4SDimitry Andric void SetCanBeActive(bool b) { m_can_activate = b; }
894480093f4SDimitry Andric
SetDelegate(const WindowDelegateSP & delegate_sp)895480093f4SDimitry Andric void SetDelegate(const WindowDelegateSP &delegate_sp) {
896480093f4SDimitry Andric m_delegate_sp = delegate_sp;
897480093f4SDimitry Andric }
898480093f4SDimitry Andric
GetParent() const899480093f4SDimitry Andric Window *GetParent() const { return m_parent; }
900480093f4SDimitry Andric
IsActive() const901480093f4SDimitry Andric bool IsActive() const {
902480093f4SDimitry Andric if (m_parent)
903480093f4SDimitry Andric return m_parent->GetActiveWindow().get() == this;
904480093f4SDimitry Andric else
905480093f4SDimitry Andric return true; // Top level window is always active
906480093f4SDimitry Andric }
907480093f4SDimitry Andric
SelectNextWindowAsActive()908480093f4SDimitry Andric void SelectNextWindowAsActive() {
909480093f4SDimitry Andric // Move active focus to next window
910af732203SDimitry Andric const int num_subwindows = m_subwindows.size();
911af732203SDimitry Andric int start_idx = 0;
912af732203SDimitry Andric if (m_curr_active_window_idx != UINT32_MAX) {
913480093f4SDimitry Andric m_prev_active_window_idx = m_curr_active_window_idx;
914af732203SDimitry Andric start_idx = m_curr_active_window_idx + 1;
915af732203SDimitry Andric }
916af732203SDimitry Andric for (int idx = start_idx; idx < num_subwindows; ++idx) {
917480093f4SDimitry Andric if (m_subwindows[idx]->GetCanBeActive()) {
918480093f4SDimitry Andric m_curr_active_window_idx = idx;
919af732203SDimitry Andric return;
920480093f4SDimitry Andric }
921480093f4SDimitry Andric }
922af732203SDimitry Andric for (int idx = 0; idx < start_idx; ++idx) {
923480093f4SDimitry Andric if (m_subwindows[idx]->GetCanBeActive()) {
924480093f4SDimitry Andric m_curr_active_window_idx = idx;
925480093f4SDimitry Andric break;
926480093f4SDimitry Andric }
927480093f4SDimitry Andric }
928480093f4SDimitry Andric }
929af732203SDimitry Andric
SelectPreviousWindowAsActive()930af732203SDimitry Andric void SelectPreviousWindowAsActive() {
931af732203SDimitry Andric // Move active focus to previous window
932af732203SDimitry Andric const int num_subwindows = m_subwindows.size();
933af732203SDimitry Andric int start_idx = num_subwindows - 1;
934af732203SDimitry Andric if (m_curr_active_window_idx != UINT32_MAX) {
935480093f4SDimitry Andric m_prev_active_window_idx = m_curr_active_window_idx;
936af732203SDimitry Andric start_idx = m_curr_active_window_idx - 1;
937af732203SDimitry Andric }
938af732203SDimitry Andric for (int idx = start_idx; idx >= 0; --idx) {
939af732203SDimitry Andric if (m_subwindows[idx]->GetCanBeActive()) {
940af732203SDimitry Andric m_curr_active_window_idx = idx;
941af732203SDimitry Andric return;
942af732203SDimitry Andric }
943af732203SDimitry Andric }
944af732203SDimitry Andric for (int idx = num_subwindows - 1; idx > start_idx; --idx) {
945480093f4SDimitry Andric if (m_subwindows[idx]->GetCanBeActive()) {
946480093f4SDimitry Andric m_curr_active_window_idx = idx;
947480093f4SDimitry Andric break;
948480093f4SDimitry Andric }
949480093f4SDimitry Andric }
950480093f4SDimitry Andric }
951480093f4SDimitry Andric
GetName() const952480093f4SDimitry Andric const char *GetName() const { return m_name.c_str(); }
953480093f4SDimitry Andric
954480093f4SDimitry Andric protected:
955480093f4SDimitry Andric std::string m_name;
956480093f4SDimitry Andric PANEL *m_panel;
957480093f4SDimitry Andric Window *m_parent;
958480093f4SDimitry Andric Windows m_subwindows;
959480093f4SDimitry Andric WindowDelegateSP m_delegate_sp;
960480093f4SDimitry Andric uint32_t m_curr_active_window_idx;
961480093f4SDimitry Andric uint32_t m_prev_active_window_idx;
962480093f4SDimitry Andric bool m_delete;
963480093f4SDimitry Andric bool m_needs_update;
964480093f4SDimitry Andric bool m_can_activate;
965480093f4SDimitry Andric bool m_is_subwin;
966480093f4SDimitry Andric
967480093f4SDimitry Andric private:
9685ffd83dbSDimitry Andric Window(const Window &) = delete;
9695ffd83dbSDimitry Andric const Window &operator=(const Window &) = delete;
970480093f4SDimitry Andric };
971480093f4SDimitry Andric
972*5f7ddb14SDimitry Andric class DerivedWindow : public Surface {
973*5f7ddb14SDimitry Andric public:
DerivedWindow(Window & window,Rect bounds)974*5f7ddb14SDimitry Andric DerivedWindow(Window &window, Rect bounds) {
975*5f7ddb14SDimitry Andric m_window = ::derwin(window.get(), bounds.size.height, bounds.size.width,
976*5f7ddb14SDimitry Andric bounds.origin.y, bounds.origin.x);
977*5f7ddb14SDimitry Andric }
DerivedWindow(DerivedWindow & derived_window,Rect bounds)978*5f7ddb14SDimitry Andric DerivedWindow(DerivedWindow &derived_window, Rect bounds) {
979*5f7ddb14SDimitry Andric m_window = ::derwin(derived_window.get(), bounds.size.height,
980*5f7ddb14SDimitry Andric bounds.size.width, bounds.origin.y, bounds.origin.x);
981*5f7ddb14SDimitry Andric }
982*5f7ddb14SDimitry Andric
~DerivedWindow()983*5f7ddb14SDimitry Andric ~DerivedWindow() { ::delwin(m_window); }
984*5f7ddb14SDimitry Andric };
985*5f7ddb14SDimitry Andric
986*5f7ddb14SDimitry Andric /////////
987*5f7ddb14SDimitry Andric // Forms
988*5f7ddb14SDimitry Andric /////////
989*5f7ddb14SDimitry Andric
990*5f7ddb14SDimitry Andric // A scroll context defines a vertical region that needs to be visible in a
991*5f7ddb14SDimitry Andric // scrolling area. The region is defined by the index of the start and end lines
992*5f7ddb14SDimitry Andric // of the region. The start and end lines may be equal, in which case, the
993*5f7ddb14SDimitry Andric // region is a single line.
994*5f7ddb14SDimitry Andric struct ScrollContext {
995*5f7ddb14SDimitry Andric int start;
996*5f7ddb14SDimitry Andric int end;
997*5f7ddb14SDimitry Andric
ScrollContextcurses::ScrollContext998*5f7ddb14SDimitry Andric ScrollContext(int line) : start(line), end(line) {}
ScrollContextcurses::ScrollContext999*5f7ddb14SDimitry Andric ScrollContext(int _start, int _end) : start(_start), end(_end) {}
1000*5f7ddb14SDimitry Andric
Offsetcurses::ScrollContext1001*5f7ddb14SDimitry Andric void Offset(int offset) {
1002*5f7ddb14SDimitry Andric start += offset;
1003*5f7ddb14SDimitry Andric end += offset;
1004*5f7ddb14SDimitry Andric }
1005*5f7ddb14SDimitry Andric };
1006*5f7ddb14SDimitry Andric
1007*5f7ddb14SDimitry Andric class FieldDelegate {
1008*5f7ddb14SDimitry Andric public:
1009*5f7ddb14SDimitry Andric virtual ~FieldDelegate() = default;
1010*5f7ddb14SDimitry Andric
1011*5f7ddb14SDimitry Andric // Returns the number of lines needed to draw the field. The draw method will
1012*5f7ddb14SDimitry Andric // be given a surface that have exactly this number of lines.
1013*5f7ddb14SDimitry Andric virtual int FieldDelegateGetHeight() = 0;
1014*5f7ddb14SDimitry Andric
1015*5f7ddb14SDimitry Andric // Returns the scroll context in the local coordinates of the field. By
1016*5f7ddb14SDimitry Andric // default, the scroll context spans the whole field. Bigger fields with
1017*5f7ddb14SDimitry Andric // internal navigation should override this method to provide a finer context.
1018*5f7ddb14SDimitry Andric // Typical override methods would first get the scroll context of the internal
1019*5f7ddb14SDimitry Andric // element then add the offset of the element in the field.
FieldDelegateGetScrollContext()1020*5f7ddb14SDimitry Andric virtual ScrollContext FieldDelegateGetScrollContext() {
1021*5f7ddb14SDimitry Andric return ScrollContext(0, FieldDelegateGetHeight() - 1);
1022*5f7ddb14SDimitry Andric }
1023*5f7ddb14SDimitry Andric
1024*5f7ddb14SDimitry Andric // Draw the field in the given subpad surface. The surface have a height that
1025*5f7ddb14SDimitry Andric // is equal to the height returned by FieldDelegateGetHeight(). If the field
1026*5f7ddb14SDimitry Andric // is selected in the form window, then is_selected will be true.
1027*5f7ddb14SDimitry Andric virtual void FieldDelegateDraw(SubPad &surface, bool is_selected) = 0;
1028*5f7ddb14SDimitry Andric
1029*5f7ddb14SDimitry Andric // Handle the key that wasn't handled by the form window or a container field.
FieldDelegateHandleChar(int key)1030*5f7ddb14SDimitry Andric virtual HandleCharResult FieldDelegateHandleChar(int key) {
1031*5f7ddb14SDimitry Andric return eKeyNotHandled;
1032*5f7ddb14SDimitry Andric }
1033*5f7ddb14SDimitry Andric
1034*5f7ddb14SDimitry Andric // This is executed once the user exists the field, that is, once the user
1035*5f7ddb14SDimitry Andric // navigates to the next or the previous field. This is particularly useful to
1036*5f7ddb14SDimitry Andric // do in-field validation and error setting. Fields with internal navigation
1037*5f7ddb14SDimitry Andric // should call this method on their fields.
FieldDelegateExitCallback()1038*5f7ddb14SDimitry Andric virtual void FieldDelegateExitCallback() { return; }
1039*5f7ddb14SDimitry Andric
1040*5f7ddb14SDimitry Andric // Fields may have internal navigation, for instance, a List Field have
1041*5f7ddb14SDimitry Andric // multiple internal elements, which needs to be navigated. To allow for this
1042*5f7ddb14SDimitry Andric // mechanism, the window shouldn't handle the navigation keys all the time,
1043*5f7ddb14SDimitry Andric // and instead call the key handing method of the selected field. It should
1044*5f7ddb14SDimitry Andric // only handle the navigation keys when the field contains a single element or
1045*5f7ddb14SDimitry Andric // have the last or first element selected depending on if the user is
1046*5f7ddb14SDimitry Andric // navigating forward or backward. Additionally, once a field is selected in
1047*5f7ddb14SDimitry Andric // the forward or backward direction, its first or last internal element
1048*5f7ddb14SDimitry Andric // should be selected. The following methods implements those mechanisms.
1049*5f7ddb14SDimitry Andric
1050*5f7ddb14SDimitry Andric // Returns true if the first element in the field is selected or if the field
1051*5f7ddb14SDimitry Andric // contains a single element.
FieldDelegateOnFirstOrOnlyElement()1052*5f7ddb14SDimitry Andric virtual bool FieldDelegateOnFirstOrOnlyElement() { return true; }
1053*5f7ddb14SDimitry Andric
1054*5f7ddb14SDimitry Andric // Returns true if the last element in the field is selected or if the field
1055*5f7ddb14SDimitry Andric // contains a single element.
FieldDelegateOnLastOrOnlyElement()1056*5f7ddb14SDimitry Andric virtual bool FieldDelegateOnLastOrOnlyElement() { return true; }
1057*5f7ddb14SDimitry Andric
1058*5f7ddb14SDimitry Andric // Select the first element in the field if multiple elements exists.
FieldDelegateSelectFirstElement()1059*5f7ddb14SDimitry Andric virtual void FieldDelegateSelectFirstElement() { return; }
1060*5f7ddb14SDimitry Andric
1061*5f7ddb14SDimitry Andric // Select the last element in the field if multiple elements exists.
FieldDelegateSelectLastElement()1062*5f7ddb14SDimitry Andric virtual void FieldDelegateSelectLastElement() { return; }
1063*5f7ddb14SDimitry Andric
1064*5f7ddb14SDimitry Andric // Returns true if the field has an error, false otherwise.
FieldDelegateHasError()1065*5f7ddb14SDimitry Andric virtual bool FieldDelegateHasError() { return false; }
1066*5f7ddb14SDimitry Andric
FieldDelegateIsVisible()1067*5f7ddb14SDimitry Andric bool FieldDelegateIsVisible() { return m_is_visible; }
1068*5f7ddb14SDimitry Andric
FieldDelegateHide()1069*5f7ddb14SDimitry Andric void FieldDelegateHide() { m_is_visible = false; }
1070*5f7ddb14SDimitry Andric
FieldDelegateShow()1071*5f7ddb14SDimitry Andric void FieldDelegateShow() { m_is_visible = true; }
1072*5f7ddb14SDimitry Andric
1073*5f7ddb14SDimitry Andric protected:
1074*5f7ddb14SDimitry Andric bool m_is_visible = true;
1075*5f7ddb14SDimitry Andric };
1076*5f7ddb14SDimitry Andric
1077*5f7ddb14SDimitry Andric typedef std::unique_ptr<FieldDelegate> FieldDelegateUP;
1078*5f7ddb14SDimitry Andric
1079*5f7ddb14SDimitry Andric class TextFieldDelegate : public FieldDelegate {
1080*5f7ddb14SDimitry Andric public:
TextFieldDelegate(const char * label,const char * content,bool required)1081*5f7ddb14SDimitry Andric TextFieldDelegate(const char *label, const char *content, bool required)
1082*5f7ddb14SDimitry Andric : m_label(label), m_required(required), m_cursor_position(0),
1083*5f7ddb14SDimitry Andric m_first_visibile_char(0) {
1084*5f7ddb14SDimitry Andric if (content)
1085*5f7ddb14SDimitry Andric m_content = content;
1086*5f7ddb14SDimitry Andric }
1087*5f7ddb14SDimitry Andric
1088*5f7ddb14SDimitry Andric // Text fields are drawn as titled boxes of a single line, with a possible
1089*5f7ddb14SDimitry Andric // error messages at the end.
1090*5f7ddb14SDimitry Andric //
1091*5f7ddb14SDimitry Andric // __[Label]___________
1092*5f7ddb14SDimitry Andric // | |
1093*5f7ddb14SDimitry Andric // |__________________|
1094*5f7ddb14SDimitry Andric // - Error message if it exists.
1095*5f7ddb14SDimitry Andric
1096*5f7ddb14SDimitry Andric // The text field has a height of 3 lines. 2 lines for borders and 1 line for
1097*5f7ddb14SDimitry Andric // the content.
GetFieldHeight()1098*5f7ddb14SDimitry Andric int GetFieldHeight() { return 3; }
1099*5f7ddb14SDimitry Andric
1100*5f7ddb14SDimitry Andric // The text field has a full height of 3 or 4 lines. 3 lines for the actual
1101*5f7ddb14SDimitry Andric // field and an optional line for an error if it exists.
FieldDelegateGetHeight()1102*5f7ddb14SDimitry Andric int FieldDelegateGetHeight() override {
1103*5f7ddb14SDimitry Andric int height = GetFieldHeight();
1104*5f7ddb14SDimitry Andric if (FieldDelegateHasError())
1105*5f7ddb14SDimitry Andric height++;
1106*5f7ddb14SDimitry Andric return height;
1107*5f7ddb14SDimitry Andric }
1108*5f7ddb14SDimitry Andric
1109*5f7ddb14SDimitry Andric // Get the cursor X position in the surface coordinate.
GetCursorXPosition()1110*5f7ddb14SDimitry Andric int GetCursorXPosition() { return m_cursor_position - m_first_visibile_char; }
1111*5f7ddb14SDimitry Andric
GetContentLength()1112*5f7ddb14SDimitry Andric int GetContentLength() { return m_content.length(); }
1113*5f7ddb14SDimitry Andric
DrawContent(SubPad & surface,bool is_selected)1114*5f7ddb14SDimitry Andric void DrawContent(SubPad &surface, bool is_selected) {
1115*5f7ddb14SDimitry Andric surface.MoveCursor(0, 0);
1116*5f7ddb14SDimitry Andric const char *text = m_content.c_str() + m_first_visibile_char;
1117*5f7ddb14SDimitry Andric surface.PutCString(text, surface.GetWidth());
1118*5f7ddb14SDimitry Andric m_last_drawn_content_width = surface.GetWidth();
1119*5f7ddb14SDimitry Andric
1120*5f7ddb14SDimitry Andric // Highlight the cursor.
1121*5f7ddb14SDimitry Andric surface.MoveCursor(GetCursorXPosition(), 0);
1122*5f7ddb14SDimitry Andric if (is_selected)
1123*5f7ddb14SDimitry Andric surface.AttributeOn(A_REVERSE);
1124*5f7ddb14SDimitry Andric if (m_cursor_position == GetContentLength())
1125*5f7ddb14SDimitry Andric // Cursor is past the last character. Highlight an empty space.
1126*5f7ddb14SDimitry Andric surface.PutChar(' ');
1127*5f7ddb14SDimitry Andric else
1128*5f7ddb14SDimitry Andric surface.PutChar(m_content[m_cursor_position]);
1129*5f7ddb14SDimitry Andric if (is_selected)
1130*5f7ddb14SDimitry Andric surface.AttributeOff(A_REVERSE);
1131*5f7ddb14SDimitry Andric }
1132*5f7ddb14SDimitry Andric
DrawField(SubPad & surface,bool is_selected)1133*5f7ddb14SDimitry Andric void DrawField(SubPad &surface, bool is_selected) {
1134*5f7ddb14SDimitry Andric surface.TitledBox(m_label.c_str());
1135*5f7ddb14SDimitry Andric
1136*5f7ddb14SDimitry Andric Rect content_bounds = surface.GetFrame();
1137*5f7ddb14SDimitry Andric content_bounds.Inset(1, 1);
1138*5f7ddb14SDimitry Andric SubPad content_surface = SubPad(surface, content_bounds);
1139*5f7ddb14SDimitry Andric
1140*5f7ddb14SDimitry Andric DrawContent(content_surface, is_selected);
1141*5f7ddb14SDimitry Andric }
1142*5f7ddb14SDimitry Andric
DrawError(SubPad & surface)1143*5f7ddb14SDimitry Andric void DrawError(SubPad &surface) {
1144*5f7ddb14SDimitry Andric if (!FieldDelegateHasError())
1145*5f7ddb14SDimitry Andric return;
1146*5f7ddb14SDimitry Andric surface.MoveCursor(0, 0);
1147*5f7ddb14SDimitry Andric surface.AttributeOn(COLOR_PAIR(RedOnBlack));
1148*5f7ddb14SDimitry Andric surface.PutChar(ACS_DIAMOND);
1149*5f7ddb14SDimitry Andric surface.PutChar(' ');
1150*5f7ddb14SDimitry Andric surface.PutCStringTruncated(1, GetError().c_str());
1151*5f7ddb14SDimitry Andric surface.AttributeOff(COLOR_PAIR(RedOnBlack));
1152*5f7ddb14SDimitry Andric }
1153*5f7ddb14SDimitry Andric
FieldDelegateDraw(SubPad & surface,bool is_selected)1154*5f7ddb14SDimitry Andric void FieldDelegateDraw(SubPad &surface, bool is_selected) override {
1155*5f7ddb14SDimitry Andric Rect frame = surface.GetFrame();
1156*5f7ddb14SDimitry Andric Rect field_bounds, error_bounds;
1157*5f7ddb14SDimitry Andric frame.HorizontalSplit(GetFieldHeight(), field_bounds, error_bounds);
1158*5f7ddb14SDimitry Andric SubPad field_surface = SubPad(surface, field_bounds);
1159*5f7ddb14SDimitry Andric SubPad error_surface = SubPad(surface, error_bounds);
1160*5f7ddb14SDimitry Andric
1161*5f7ddb14SDimitry Andric DrawField(field_surface, is_selected);
1162*5f7ddb14SDimitry Andric DrawError(error_surface);
1163*5f7ddb14SDimitry Andric }
1164*5f7ddb14SDimitry Andric
1165*5f7ddb14SDimitry Andric // The cursor is allowed to move one character past the string.
1166*5f7ddb14SDimitry Andric // m_cursor_position is in range [0, GetContentLength()].
MoveCursorRight()1167*5f7ddb14SDimitry Andric void MoveCursorRight() {
1168*5f7ddb14SDimitry Andric if (m_cursor_position < GetContentLength())
1169*5f7ddb14SDimitry Andric m_cursor_position++;
1170*5f7ddb14SDimitry Andric }
1171*5f7ddb14SDimitry Andric
MoveCursorLeft()1172*5f7ddb14SDimitry Andric void MoveCursorLeft() {
1173*5f7ddb14SDimitry Andric if (m_cursor_position > 0)
1174*5f7ddb14SDimitry Andric m_cursor_position--;
1175*5f7ddb14SDimitry Andric }
1176*5f7ddb14SDimitry Andric
1177*5f7ddb14SDimitry Andric // If the cursor moved past the last visible character, scroll right by one
1178*5f7ddb14SDimitry Andric // character.
ScrollRightIfNeeded()1179*5f7ddb14SDimitry Andric void ScrollRightIfNeeded() {
1180*5f7ddb14SDimitry Andric if (m_cursor_position - m_first_visibile_char == m_last_drawn_content_width)
1181*5f7ddb14SDimitry Andric m_first_visibile_char++;
1182*5f7ddb14SDimitry Andric }
1183*5f7ddb14SDimitry Andric
ScrollLeft()1184*5f7ddb14SDimitry Andric void ScrollLeft() {
1185*5f7ddb14SDimitry Andric if (m_first_visibile_char > 0)
1186*5f7ddb14SDimitry Andric m_first_visibile_char--;
1187*5f7ddb14SDimitry Andric }
1188*5f7ddb14SDimitry Andric
1189*5f7ddb14SDimitry Andric // If the cursor moved past the first visible character, scroll left by one
1190*5f7ddb14SDimitry Andric // character.
ScrollLeftIfNeeded()1191*5f7ddb14SDimitry Andric void ScrollLeftIfNeeded() {
1192*5f7ddb14SDimitry Andric if (m_cursor_position < m_first_visibile_char)
1193*5f7ddb14SDimitry Andric m_first_visibile_char--;
1194*5f7ddb14SDimitry Andric }
1195*5f7ddb14SDimitry Andric
1196*5f7ddb14SDimitry Andric // Insert a character at the current cursor position, advance the cursor
1197*5f7ddb14SDimitry Andric // position, and make sure to scroll right if needed.
InsertChar(char character)1198*5f7ddb14SDimitry Andric void InsertChar(char character) {
1199*5f7ddb14SDimitry Andric m_content.insert(m_cursor_position, 1, character);
1200*5f7ddb14SDimitry Andric m_cursor_position++;
1201*5f7ddb14SDimitry Andric ScrollRightIfNeeded();
1202*5f7ddb14SDimitry Andric }
1203*5f7ddb14SDimitry Andric
1204*5f7ddb14SDimitry Andric // Remove the character before the cursor position, retreat the cursor
1205*5f7ddb14SDimitry Andric // position, and make sure to scroll left if needed.
RemoveChar()1206*5f7ddb14SDimitry Andric void RemoveChar() {
1207*5f7ddb14SDimitry Andric if (m_cursor_position == 0)
1208*5f7ddb14SDimitry Andric return;
1209*5f7ddb14SDimitry Andric
1210*5f7ddb14SDimitry Andric m_content.erase(m_cursor_position - 1, 1);
1211*5f7ddb14SDimitry Andric m_cursor_position--;
1212*5f7ddb14SDimitry Andric ScrollLeft();
1213*5f7ddb14SDimitry Andric }
1214*5f7ddb14SDimitry Andric
1215*5f7ddb14SDimitry Andric // True if the key represents a char that can be inserted in the field
1216*5f7ddb14SDimitry Andric // content, false otherwise.
IsAcceptableChar(int key)1217*5f7ddb14SDimitry Andric virtual bool IsAcceptableChar(int key) { return isprint(key); }
1218*5f7ddb14SDimitry Andric
FieldDelegateHandleChar(int key)1219*5f7ddb14SDimitry Andric HandleCharResult FieldDelegateHandleChar(int key) override {
1220*5f7ddb14SDimitry Andric if (IsAcceptableChar(key)) {
1221*5f7ddb14SDimitry Andric ClearError();
1222*5f7ddb14SDimitry Andric InsertChar((char)key);
1223*5f7ddb14SDimitry Andric return eKeyHandled;
1224*5f7ddb14SDimitry Andric }
1225*5f7ddb14SDimitry Andric
1226*5f7ddb14SDimitry Andric switch (key) {
1227*5f7ddb14SDimitry Andric case KEY_RIGHT:
1228*5f7ddb14SDimitry Andric MoveCursorRight();
1229*5f7ddb14SDimitry Andric ScrollRightIfNeeded();
1230*5f7ddb14SDimitry Andric return eKeyHandled;
1231*5f7ddb14SDimitry Andric case KEY_LEFT:
1232*5f7ddb14SDimitry Andric MoveCursorLeft();
1233*5f7ddb14SDimitry Andric ScrollLeftIfNeeded();
1234*5f7ddb14SDimitry Andric return eKeyHandled;
1235*5f7ddb14SDimitry Andric case KEY_BACKSPACE:
1236*5f7ddb14SDimitry Andric ClearError();
1237*5f7ddb14SDimitry Andric RemoveChar();
1238*5f7ddb14SDimitry Andric return eKeyHandled;
1239*5f7ddb14SDimitry Andric default:
1240*5f7ddb14SDimitry Andric break;
1241*5f7ddb14SDimitry Andric }
1242*5f7ddb14SDimitry Andric return eKeyNotHandled;
1243*5f7ddb14SDimitry Andric }
1244*5f7ddb14SDimitry Andric
FieldDelegateHasError()1245*5f7ddb14SDimitry Andric bool FieldDelegateHasError() override { return !m_error.empty(); }
1246*5f7ddb14SDimitry Andric
FieldDelegateExitCallback()1247*5f7ddb14SDimitry Andric void FieldDelegateExitCallback() override {
1248*5f7ddb14SDimitry Andric if (!IsSpecified() && m_required)
1249*5f7ddb14SDimitry Andric SetError("This field is required!");
1250*5f7ddb14SDimitry Andric }
1251*5f7ddb14SDimitry Andric
IsSpecified()1252*5f7ddb14SDimitry Andric bool IsSpecified() { return !m_content.empty(); }
1253*5f7ddb14SDimitry Andric
ClearError()1254*5f7ddb14SDimitry Andric void ClearError() { m_error.clear(); }
1255*5f7ddb14SDimitry Andric
GetError()1256*5f7ddb14SDimitry Andric const std::string &GetError() { return m_error; }
1257*5f7ddb14SDimitry Andric
SetError(const char * error)1258*5f7ddb14SDimitry Andric void SetError(const char *error) { m_error = error; }
1259*5f7ddb14SDimitry Andric
GetText()1260*5f7ddb14SDimitry Andric const std::string &GetText() { return m_content; }
1261*5f7ddb14SDimitry Andric
1262*5f7ddb14SDimitry Andric protected:
1263*5f7ddb14SDimitry Andric std::string m_label;
1264*5f7ddb14SDimitry Andric bool m_required;
1265*5f7ddb14SDimitry Andric // The position of the top left corner character of the border.
1266*5f7ddb14SDimitry Andric std::string m_content;
1267*5f7ddb14SDimitry Andric // The cursor position in the content string itself. Can be in the range
1268*5f7ddb14SDimitry Andric // [0, GetContentLength()].
1269*5f7ddb14SDimitry Andric int m_cursor_position;
1270*5f7ddb14SDimitry Andric // The index of the first visible character in the content.
1271*5f7ddb14SDimitry Andric int m_first_visibile_char;
1272*5f7ddb14SDimitry Andric // The width of the fields content that was last drawn. Width can change, so
1273*5f7ddb14SDimitry Andric // this is used to determine if scrolling is needed dynamically.
1274*5f7ddb14SDimitry Andric int m_last_drawn_content_width;
1275*5f7ddb14SDimitry Andric // Optional error message. If empty, field is considered to have no error.
1276*5f7ddb14SDimitry Andric std::string m_error;
1277*5f7ddb14SDimitry Andric };
1278*5f7ddb14SDimitry Andric
1279*5f7ddb14SDimitry Andric class IntegerFieldDelegate : public TextFieldDelegate {
1280*5f7ddb14SDimitry Andric public:
IntegerFieldDelegate(const char * label,int content,bool required)1281*5f7ddb14SDimitry Andric IntegerFieldDelegate(const char *label, int content, bool required)
1282*5f7ddb14SDimitry Andric : TextFieldDelegate(label, std::to_string(content).c_str(), required) {}
1283*5f7ddb14SDimitry Andric
1284*5f7ddb14SDimitry Andric // Only accept digits.
IsAcceptableChar(int key)1285*5f7ddb14SDimitry Andric bool IsAcceptableChar(int key) override { return isdigit(key); }
1286*5f7ddb14SDimitry Andric
1287*5f7ddb14SDimitry Andric // Returns the integer content of the field.
GetInteger()1288*5f7ddb14SDimitry Andric int GetInteger() { return std::stoi(m_content); }
1289*5f7ddb14SDimitry Andric };
1290*5f7ddb14SDimitry Andric
1291*5f7ddb14SDimitry Andric class FileFieldDelegate : public TextFieldDelegate {
1292*5f7ddb14SDimitry Andric public:
FileFieldDelegate(const char * label,const char * content,bool need_to_exist,bool required)1293*5f7ddb14SDimitry Andric FileFieldDelegate(const char *label, const char *content, bool need_to_exist,
1294*5f7ddb14SDimitry Andric bool required)
1295*5f7ddb14SDimitry Andric : TextFieldDelegate(label, content, required),
1296*5f7ddb14SDimitry Andric m_need_to_exist(need_to_exist) {}
1297*5f7ddb14SDimitry Andric
FieldDelegateExitCallback()1298*5f7ddb14SDimitry Andric void FieldDelegateExitCallback() override {
1299*5f7ddb14SDimitry Andric TextFieldDelegate::FieldDelegateExitCallback();
1300*5f7ddb14SDimitry Andric if (!IsSpecified())
1301*5f7ddb14SDimitry Andric return;
1302*5f7ddb14SDimitry Andric
1303*5f7ddb14SDimitry Andric if (!m_need_to_exist)
1304*5f7ddb14SDimitry Andric return;
1305*5f7ddb14SDimitry Andric
1306*5f7ddb14SDimitry Andric FileSpec file = GetResolvedFileSpec();
1307*5f7ddb14SDimitry Andric if (!FileSystem::Instance().Exists(file)) {
1308*5f7ddb14SDimitry Andric SetError("File doesn't exist!");
1309*5f7ddb14SDimitry Andric return;
1310*5f7ddb14SDimitry Andric }
1311*5f7ddb14SDimitry Andric if (FileSystem::Instance().IsDirectory(file)) {
1312*5f7ddb14SDimitry Andric SetError("Not a file!");
1313*5f7ddb14SDimitry Andric return;
1314*5f7ddb14SDimitry Andric }
1315*5f7ddb14SDimitry Andric }
1316*5f7ddb14SDimitry Andric
GetFileSpec()1317*5f7ddb14SDimitry Andric FileSpec GetFileSpec() {
1318*5f7ddb14SDimitry Andric FileSpec file_spec(GetPath());
1319*5f7ddb14SDimitry Andric return file_spec;
1320*5f7ddb14SDimitry Andric }
1321*5f7ddb14SDimitry Andric
GetResolvedFileSpec()1322*5f7ddb14SDimitry Andric FileSpec GetResolvedFileSpec() {
1323*5f7ddb14SDimitry Andric FileSpec file_spec(GetPath());
1324*5f7ddb14SDimitry Andric FileSystem::Instance().Resolve(file_spec);
1325*5f7ddb14SDimitry Andric return file_spec;
1326*5f7ddb14SDimitry Andric }
1327*5f7ddb14SDimitry Andric
GetPath()1328*5f7ddb14SDimitry Andric const std::string &GetPath() { return m_content; }
1329*5f7ddb14SDimitry Andric
1330*5f7ddb14SDimitry Andric protected:
1331*5f7ddb14SDimitry Andric bool m_need_to_exist;
1332*5f7ddb14SDimitry Andric };
1333*5f7ddb14SDimitry Andric
1334*5f7ddb14SDimitry Andric class DirectoryFieldDelegate : public TextFieldDelegate {
1335*5f7ddb14SDimitry Andric public:
DirectoryFieldDelegate(const char * label,const char * content,bool need_to_exist,bool required)1336*5f7ddb14SDimitry Andric DirectoryFieldDelegate(const char *label, const char *content,
1337*5f7ddb14SDimitry Andric bool need_to_exist, bool required)
1338*5f7ddb14SDimitry Andric : TextFieldDelegate(label, content, required),
1339*5f7ddb14SDimitry Andric m_need_to_exist(need_to_exist) {}
1340*5f7ddb14SDimitry Andric
FieldDelegateExitCallback()1341*5f7ddb14SDimitry Andric void FieldDelegateExitCallback() override {
1342*5f7ddb14SDimitry Andric TextFieldDelegate::FieldDelegateExitCallback();
1343*5f7ddb14SDimitry Andric if (!IsSpecified())
1344*5f7ddb14SDimitry Andric return;
1345*5f7ddb14SDimitry Andric
1346*5f7ddb14SDimitry Andric if (!m_need_to_exist)
1347*5f7ddb14SDimitry Andric return;
1348*5f7ddb14SDimitry Andric
1349*5f7ddb14SDimitry Andric FileSpec file = GetResolvedFileSpec();
1350*5f7ddb14SDimitry Andric if (!FileSystem::Instance().Exists(file)) {
1351*5f7ddb14SDimitry Andric SetError("Directory doesn't exist!");
1352*5f7ddb14SDimitry Andric return;
1353*5f7ddb14SDimitry Andric }
1354*5f7ddb14SDimitry Andric if (!FileSystem::Instance().IsDirectory(file)) {
1355*5f7ddb14SDimitry Andric SetError("Not a directory!");
1356*5f7ddb14SDimitry Andric return;
1357*5f7ddb14SDimitry Andric }
1358*5f7ddb14SDimitry Andric }
1359*5f7ddb14SDimitry Andric
GetFileSpec()1360*5f7ddb14SDimitry Andric FileSpec GetFileSpec() {
1361*5f7ddb14SDimitry Andric FileSpec file_spec(GetPath());
1362*5f7ddb14SDimitry Andric return file_spec;
1363*5f7ddb14SDimitry Andric }
1364*5f7ddb14SDimitry Andric
GetResolvedFileSpec()1365*5f7ddb14SDimitry Andric FileSpec GetResolvedFileSpec() {
1366*5f7ddb14SDimitry Andric FileSpec file_spec(GetPath());
1367*5f7ddb14SDimitry Andric FileSystem::Instance().Resolve(file_spec);
1368*5f7ddb14SDimitry Andric return file_spec;
1369*5f7ddb14SDimitry Andric }
1370*5f7ddb14SDimitry Andric
GetPath()1371*5f7ddb14SDimitry Andric const std::string &GetPath() { return m_content; }
1372*5f7ddb14SDimitry Andric
1373*5f7ddb14SDimitry Andric protected:
1374*5f7ddb14SDimitry Andric bool m_need_to_exist;
1375*5f7ddb14SDimitry Andric };
1376*5f7ddb14SDimitry Andric
1377*5f7ddb14SDimitry Andric class ArchFieldDelegate : public TextFieldDelegate {
1378*5f7ddb14SDimitry Andric public:
ArchFieldDelegate(const char * label,const char * content,bool required)1379*5f7ddb14SDimitry Andric ArchFieldDelegate(const char *label, const char *content, bool required)
1380*5f7ddb14SDimitry Andric : TextFieldDelegate(label, content, required) {}
1381*5f7ddb14SDimitry Andric
FieldDelegateExitCallback()1382*5f7ddb14SDimitry Andric void FieldDelegateExitCallback() override {
1383*5f7ddb14SDimitry Andric TextFieldDelegate::FieldDelegateExitCallback();
1384*5f7ddb14SDimitry Andric if (!IsSpecified())
1385*5f7ddb14SDimitry Andric return;
1386*5f7ddb14SDimitry Andric
1387*5f7ddb14SDimitry Andric if (!GetArchSpec().IsValid())
1388*5f7ddb14SDimitry Andric SetError("Not a valid arch!");
1389*5f7ddb14SDimitry Andric }
1390*5f7ddb14SDimitry Andric
GetArchString()1391*5f7ddb14SDimitry Andric const std::string &GetArchString() { return m_content; }
1392*5f7ddb14SDimitry Andric
GetArchSpec()1393*5f7ddb14SDimitry Andric ArchSpec GetArchSpec() { return ArchSpec(GetArchString()); }
1394*5f7ddb14SDimitry Andric };
1395*5f7ddb14SDimitry Andric
1396*5f7ddb14SDimitry Andric class BooleanFieldDelegate : public FieldDelegate {
1397*5f7ddb14SDimitry Andric public:
BooleanFieldDelegate(const char * label,bool content)1398*5f7ddb14SDimitry Andric BooleanFieldDelegate(const char *label, bool content)
1399*5f7ddb14SDimitry Andric : m_label(label), m_content(content) {}
1400*5f7ddb14SDimitry Andric
1401*5f7ddb14SDimitry Andric // Boolean fields are drawn as checkboxes.
1402*5f7ddb14SDimitry Andric //
1403*5f7ddb14SDimitry Andric // [X] Label or [ ] Label
1404*5f7ddb14SDimitry Andric
1405*5f7ddb14SDimitry Andric // Boolean fields are have a single line.
FieldDelegateGetHeight()1406*5f7ddb14SDimitry Andric int FieldDelegateGetHeight() override { return 1; }
1407*5f7ddb14SDimitry Andric
FieldDelegateDraw(SubPad & surface,bool is_selected)1408*5f7ddb14SDimitry Andric void FieldDelegateDraw(SubPad &surface, bool is_selected) override {
1409*5f7ddb14SDimitry Andric surface.MoveCursor(0, 0);
1410*5f7ddb14SDimitry Andric surface.PutChar('[');
1411*5f7ddb14SDimitry Andric if (is_selected)
1412*5f7ddb14SDimitry Andric surface.AttributeOn(A_REVERSE);
1413*5f7ddb14SDimitry Andric surface.PutChar(m_content ? ACS_DIAMOND : ' ');
1414*5f7ddb14SDimitry Andric if (is_selected)
1415*5f7ddb14SDimitry Andric surface.AttributeOff(A_REVERSE);
1416*5f7ddb14SDimitry Andric surface.PutChar(']');
1417*5f7ddb14SDimitry Andric surface.PutChar(' ');
1418*5f7ddb14SDimitry Andric surface.PutCString(m_label.c_str());
1419*5f7ddb14SDimitry Andric }
1420*5f7ddb14SDimitry Andric
ToggleContent()1421*5f7ddb14SDimitry Andric void ToggleContent() { m_content = !m_content; }
1422*5f7ddb14SDimitry Andric
SetContentToTrue()1423*5f7ddb14SDimitry Andric void SetContentToTrue() { m_content = true; }
1424*5f7ddb14SDimitry Andric
SetContentToFalse()1425*5f7ddb14SDimitry Andric void SetContentToFalse() { m_content = false; }
1426*5f7ddb14SDimitry Andric
FieldDelegateHandleChar(int key)1427*5f7ddb14SDimitry Andric HandleCharResult FieldDelegateHandleChar(int key) override {
1428*5f7ddb14SDimitry Andric switch (key) {
1429*5f7ddb14SDimitry Andric case 't':
1430*5f7ddb14SDimitry Andric case '1':
1431*5f7ddb14SDimitry Andric SetContentToTrue();
1432*5f7ddb14SDimitry Andric return eKeyHandled;
1433*5f7ddb14SDimitry Andric case 'f':
1434*5f7ddb14SDimitry Andric case '0':
1435*5f7ddb14SDimitry Andric SetContentToFalse();
1436*5f7ddb14SDimitry Andric return eKeyHandled;
1437*5f7ddb14SDimitry Andric case ' ':
1438*5f7ddb14SDimitry Andric case '\r':
1439*5f7ddb14SDimitry Andric case '\n':
1440*5f7ddb14SDimitry Andric case KEY_ENTER:
1441*5f7ddb14SDimitry Andric ToggleContent();
1442*5f7ddb14SDimitry Andric return eKeyHandled;
1443*5f7ddb14SDimitry Andric default:
1444*5f7ddb14SDimitry Andric break;
1445*5f7ddb14SDimitry Andric }
1446*5f7ddb14SDimitry Andric return eKeyNotHandled;
1447*5f7ddb14SDimitry Andric }
1448*5f7ddb14SDimitry Andric
1449*5f7ddb14SDimitry Andric // Returns the boolean content of the field.
GetBoolean()1450*5f7ddb14SDimitry Andric bool GetBoolean() { return m_content; }
1451*5f7ddb14SDimitry Andric
1452*5f7ddb14SDimitry Andric protected:
1453*5f7ddb14SDimitry Andric std::string m_label;
1454*5f7ddb14SDimitry Andric bool m_content;
1455*5f7ddb14SDimitry Andric };
1456*5f7ddb14SDimitry Andric
1457*5f7ddb14SDimitry Andric class ChoicesFieldDelegate : public FieldDelegate {
1458*5f7ddb14SDimitry Andric public:
ChoicesFieldDelegate(const char * label,int number_of_visible_choices,std::vector<std::string> choices)1459*5f7ddb14SDimitry Andric ChoicesFieldDelegate(const char *label, int number_of_visible_choices,
1460*5f7ddb14SDimitry Andric std::vector<std::string> choices)
1461*5f7ddb14SDimitry Andric : m_label(label), m_number_of_visible_choices(number_of_visible_choices),
1462*5f7ddb14SDimitry Andric m_choices(choices), m_choice(0), m_first_visibile_choice(0) {}
1463*5f7ddb14SDimitry Andric
1464*5f7ddb14SDimitry Andric // Choices fields are drawn as titles boxses of a number of visible choices.
1465*5f7ddb14SDimitry Andric // The rest of the choices become visible as the user scroll. The selected
1466*5f7ddb14SDimitry Andric // choice is denoted by a diamond as the first character.
1467*5f7ddb14SDimitry Andric //
1468*5f7ddb14SDimitry Andric // __[Label]___________
1469*5f7ddb14SDimitry Andric // |-Choice 1 |
1470*5f7ddb14SDimitry Andric // | Choice 2 |
1471*5f7ddb14SDimitry Andric // | Choice 3 |
1472*5f7ddb14SDimitry Andric // |__________________|
1473*5f7ddb14SDimitry Andric
1474*5f7ddb14SDimitry Andric // Choices field have two border characters plus the number of visible
1475*5f7ddb14SDimitry Andric // choices.
FieldDelegateGetHeight()1476*5f7ddb14SDimitry Andric int FieldDelegateGetHeight() override {
1477*5f7ddb14SDimitry Andric return m_number_of_visible_choices + 2;
1478*5f7ddb14SDimitry Andric }
1479*5f7ddb14SDimitry Andric
GetNumberOfChoices()1480*5f7ddb14SDimitry Andric int GetNumberOfChoices() { return m_choices.size(); }
1481*5f7ddb14SDimitry Andric
1482*5f7ddb14SDimitry Andric // Get the index of the last visible choice.
GetLastVisibleChoice()1483*5f7ddb14SDimitry Andric int GetLastVisibleChoice() {
1484*5f7ddb14SDimitry Andric int index = m_first_visibile_choice + m_number_of_visible_choices;
1485*5f7ddb14SDimitry Andric return std::min(index, GetNumberOfChoices()) - 1;
1486*5f7ddb14SDimitry Andric }
1487*5f7ddb14SDimitry Andric
DrawContent(SubPad & surface,bool is_selected)1488*5f7ddb14SDimitry Andric void DrawContent(SubPad &surface, bool is_selected) {
1489*5f7ddb14SDimitry Andric int choices_to_draw = GetLastVisibleChoice() - m_first_visibile_choice + 1;
1490*5f7ddb14SDimitry Andric for (int i = 0; i < choices_to_draw; i++) {
1491*5f7ddb14SDimitry Andric surface.MoveCursor(0, i);
1492*5f7ddb14SDimitry Andric int current_choice = m_first_visibile_choice + i;
1493*5f7ddb14SDimitry Andric const char *text = m_choices[current_choice].c_str();
1494*5f7ddb14SDimitry Andric bool highlight = is_selected && current_choice == m_choice;
1495*5f7ddb14SDimitry Andric if (highlight)
1496*5f7ddb14SDimitry Andric surface.AttributeOn(A_REVERSE);
1497*5f7ddb14SDimitry Andric surface.PutChar(current_choice == m_choice ? ACS_DIAMOND : ' ');
1498*5f7ddb14SDimitry Andric surface.PutCString(text);
1499*5f7ddb14SDimitry Andric if (highlight)
1500*5f7ddb14SDimitry Andric surface.AttributeOff(A_REVERSE);
1501*5f7ddb14SDimitry Andric }
1502*5f7ddb14SDimitry Andric }
1503*5f7ddb14SDimitry Andric
FieldDelegateDraw(SubPad & surface,bool is_selected)1504*5f7ddb14SDimitry Andric void FieldDelegateDraw(SubPad &surface, bool is_selected) override {
1505*5f7ddb14SDimitry Andric UpdateScrolling();
1506*5f7ddb14SDimitry Andric
1507*5f7ddb14SDimitry Andric surface.TitledBox(m_label.c_str());
1508*5f7ddb14SDimitry Andric
1509*5f7ddb14SDimitry Andric Rect content_bounds = surface.GetFrame();
1510*5f7ddb14SDimitry Andric content_bounds.Inset(1, 1);
1511*5f7ddb14SDimitry Andric SubPad content_surface = SubPad(surface, content_bounds);
1512*5f7ddb14SDimitry Andric
1513*5f7ddb14SDimitry Andric DrawContent(content_surface, is_selected);
1514*5f7ddb14SDimitry Andric }
1515*5f7ddb14SDimitry Andric
SelectPrevious()1516*5f7ddb14SDimitry Andric void SelectPrevious() {
1517*5f7ddb14SDimitry Andric if (m_choice > 0)
1518*5f7ddb14SDimitry Andric m_choice--;
1519*5f7ddb14SDimitry Andric }
1520*5f7ddb14SDimitry Andric
SelectNext()1521*5f7ddb14SDimitry Andric void SelectNext() {
1522*5f7ddb14SDimitry Andric if (m_choice < GetNumberOfChoices() - 1)
1523*5f7ddb14SDimitry Andric m_choice++;
1524*5f7ddb14SDimitry Andric }
1525*5f7ddb14SDimitry Andric
UpdateScrolling()1526*5f7ddb14SDimitry Andric void UpdateScrolling() {
1527*5f7ddb14SDimitry Andric if (m_choice > GetLastVisibleChoice()) {
1528*5f7ddb14SDimitry Andric m_first_visibile_choice = m_choice - (m_number_of_visible_choices - 1);
1529*5f7ddb14SDimitry Andric return;
1530*5f7ddb14SDimitry Andric }
1531*5f7ddb14SDimitry Andric
1532*5f7ddb14SDimitry Andric if (m_choice < m_first_visibile_choice)
1533*5f7ddb14SDimitry Andric m_first_visibile_choice = m_choice;
1534*5f7ddb14SDimitry Andric }
1535*5f7ddb14SDimitry Andric
FieldDelegateHandleChar(int key)1536*5f7ddb14SDimitry Andric HandleCharResult FieldDelegateHandleChar(int key) override {
1537*5f7ddb14SDimitry Andric switch (key) {
1538*5f7ddb14SDimitry Andric case KEY_UP:
1539*5f7ddb14SDimitry Andric SelectPrevious();
1540*5f7ddb14SDimitry Andric return eKeyHandled;
1541*5f7ddb14SDimitry Andric case KEY_DOWN:
1542*5f7ddb14SDimitry Andric SelectNext();
1543*5f7ddb14SDimitry Andric return eKeyHandled;
1544*5f7ddb14SDimitry Andric default:
1545*5f7ddb14SDimitry Andric break;
1546*5f7ddb14SDimitry Andric }
1547*5f7ddb14SDimitry Andric return eKeyNotHandled;
1548*5f7ddb14SDimitry Andric }
1549*5f7ddb14SDimitry Andric
1550*5f7ddb14SDimitry Andric // Returns the content of the choice as a string.
GetChoiceContent()1551*5f7ddb14SDimitry Andric std::string GetChoiceContent() { return m_choices[m_choice]; }
1552*5f7ddb14SDimitry Andric
1553*5f7ddb14SDimitry Andric // Returns the index of the choice.
GetChoice()1554*5f7ddb14SDimitry Andric int GetChoice() { return m_choice; }
1555*5f7ddb14SDimitry Andric
SetChoice(const std::string & choice)1556*5f7ddb14SDimitry Andric void SetChoice(const std::string &choice) {
1557*5f7ddb14SDimitry Andric for (int i = 0; i < GetNumberOfChoices(); i++) {
1558*5f7ddb14SDimitry Andric if (choice == m_choices[i]) {
1559*5f7ddb14SDimitry Andric m_choice = i;
1560*5f7ddb14SDimitry Andric return;
1561*5f7ddb14SDimitry Andric }
1562*5f7ddb14SDimitry Andric }
1563*5f7ddb14SDimitry Andric }
1564*5f7ddb14SDimitry Andric
1565*5f7ddb14SDimitry Andric protected:
1566*5f7ddb14SDimitry Andric std::string m_label;
1567*5f7ddb14SDimitry Andric int m_number_of_visible_choices;
1568*5f7ddb14SDimitry Andric std::vector<std::string> m_choices;
1569*5f7ddb14SDimitry Andric // The index of the selected choice.
1570*5f7ddb14SDimitry Andric int m_choice;
1571*5f7ddb14SDimitry Andric // The index of the first visible choice in the field.
1572*5f7ddb14SDimitry Andric int m_first_visibile_choice;
1573*5f7ddb14SDimitry Andric };
1574*5f7ddb14SDimitry Andric
1575*5f7ddb14SDimitry Andric class PlatformPluginFieldDelegate : public ChoicesFieldDelegate {
1576*5f7ddb14SDimitry Andric public:
PlatformPluginFieldDelegate(Debugger & debugger)1577*5f7ddb14SDimitry Andric PlatformPluginFieldDelegate(Debugger &debugger)
1578*5f7ddb14SDimitry Andric : ChoicesFieldDelegate("Platform Plugin", 3, GetPossiblePluginNames()) {
1579*5f7ddb14SDimitry Andric PlatformSP platform_sp = debugger.GetPlatformList().GetSelectedPlatform();
1580*5f7ddb14SDimitry Andric if (platform_sp)
1581*5f7ddb14SDimitry Andric SetChoice(platform_sp->GetName().AsCString());
1582*5f7ddb14SDimitry Andric }
1583*5f7ddb14SDimitry Andric
GetPossiblePluginNames()1584*5f7ddb14SDimitry Andric std::vector<std::string> GetPossiblePluginNames() {
1585*5f7ddb14SDimitry Andric std::vector<std::string> names;
1586*5f7ddb14SDimitry Andric size_t i = 0;
1587*5f7ddb14SDimitry Andric while (auto name = PluginManager::GetPlatformPluginNameAtIndex(i++))
1588*5f7ddb14SDimitry Andric names.push_back(name);
1589*5f7ddb14SDimitry Andric return names;
1590*5f7ddb14SDimitry Andric }
1591*5f7ddb14SDimitry Andric
GetPluginName()1592*5f7ddb14SDimitry Andric std::string GetPluginName() {
1593*5f7ddb14SDimitry Andric std::string plugin_name = GetChoiceContent();
1594*5f7ddb14SDimitry Andric return plugin_name;
1595*5f7ddb14SDimitry Andric }
1596*5f7ddb14SDimitry Andric };
1597*5f7ddb14SDimitry Andric
1598*5f7ddb14SDimitry Andric class ProcessPluginFieldDelegate : public ChoicesFieldDelegate {
1599*5f7ddb14SDimitry Andric public:
ProcessPluginFieldDelegate()1600*5f7ddb14SDimitry Andric ProcessPluginFieldDelegate()
1601*5f7ddb14SDimitry Andric : ChoicesFieldDelegate("Process Plugin", 3, GetPossiblePluginNames()) {}
1602*5f7ddb14SDimitry Andric
GetPossiblePluginNames()1603*5f7ddb14SDimitry Andric std::vector<std::string> GetPossiblePluginNames() {
1604*5f7ddb14SDimitry Andric std::vector<std::string> names;
1605*5f7ddb14SDimitry Andric names.push_back("<default>");
1606*5f7ddb14SDimitry Andric
1607*5f7ddb14SDimitry Andric size_t i = 0;
1608*5f7ddb14SDimitry Andric while (auto name = PluginManager::GetProcessPluginNameAtIndex(i++))
1609*5f7ddb14SDimitry Andric names.push_back(name);
1610*5f7ddb14SDimitry Andric return names;
1611*5f7ddb14SDimitry Andric }
1612*5f7ddb14SDimitry Andric
GetPluginName()1613*5f7ddb14SDimitry Andric std::string GetPluginName() {
1614*5f7ddb14SDimitry Andric std::string plugin_name = GetChoiceContent();
1615*5f7ddb14SDimitry Andric if (plugin_name == "<default>")
1616*5f7ddb14SDimitry Andric return "";
1617*5f7ddb14SDimitry Andric return plugin_name;
1618*5f7ddb14SDimitry Andric }
1619*5f7ddb14SDimitry Andric };
1620*5f7ddb14SDimitry Andric
1621*5f7ddb14SDimitry Andric template <class T> class ListFieldDelegate : public FieldDelegate {
1622*5f7ddb14SDimitry Andric public:
ListFieldDelegate(const char * label,T default_field)1623*5f7ddb14SDimitry Andric ListFieldDelegate(const char *label, T default_field)
1624*5f7ddb14SDimitry Andric : m_label(label), m_default_field(default_field), m_selection_index(0),
1625*5f7ddb14SDimitry Andric m_selection_type(SelectionType::NewButton) {}
1626*5f7ddb14SDimitry Andric
1627*5f7ddb14SDimitry Andric // Signify which element is selected. If a field or a remove button is
1628*5f7ddb14SDimitry Andric // selected, then m_selection_index signifies the particular field that
1629*5f7ddb14SDimitry Andric // is selected or the field that the remove button belongs to.
1630*5f7ddb14SDimitry Andric enum class SelectionType { Field, RemoveButton, NewButton };
1631*5f7ddb14SDimitry Andric
1632*5f7ddb14SDimitry Andric // A List field is drawn as a titled box of a number of other fields of the
1633*5f7ddb14SDimitry Andric // same type. Each field has a Remove button next to it that removes the
1634*5f7ddb14SDimitry Andric // corresponding field. Finally, the last line contains a New button to add a
1635*5f7ddb14SDimitry Andric // new field.
1636*5f7ddb14SDimitry Andric //
1637*5f7ddb14SDimitry Andric // __[Label]___________
1638*5f7ddb14SDimitry Andric // | Field 0 [Remove] |
1639*5f7ddb14SDimitry Andric // | Field 1 [Remove] |
1640*5f7ddb14SDimitry Andric // | Field 2 [Remove] |
1641*5f7ddb14SDimitry Andric // | [New] |
1642*5f7ddb14SDimitry Andric // |__________________|
1643*5f7ddb14SDimitry Andric
1644*5f7ddb14SDimitry Andric // List fields have two lines for border characters, 1 line for the New
1645*5f7ddb14SDimitry Andric // button, and the total height of the available fields.
FieldDelegateGetHeight()1646*5f7ddb14SDimitry Andric int FieldDelegateGetHeight() override {
1647*5f7ddb14SDimitry Andric // 2 border characters.
1648*5f7ddb14SDimitry Andric int height = 2;
1649*5f7ddb14SDimitry Andric // Total height of the fields.
1650*5f7ddb14SDimitry Andric for (int i = 0; i < GetNumberOfFields(); i++) {
1651*5f7ddb14SDimitry Andric height += m_fields[i].FieldDelegateGetHeight();
1652*5f7ddb14SDimitry Andric }
1653*5f7ddb14SDimitry Andric // A line for the New button.
1654*5f7ddb14SDimitry Andric height++;
1655*5f7ddb14SDimitry Andric return height;
1656*5f7ddb14SDimitry Andric }
1657*5f7ddb14SDimitry Andric
FieldDelegateGetScrollContext()1658*5f7ddb14SDimitry Andric ScrollContext FieldDelegateGetScrollContext() override {
1659*5f7ddb14SDimitry Andric int height = FieldDelegateGetHeight();
1660*5f7ddb14SDimitry Andric if (m_selection_type == SelectionType::NewButton)
1661*5f7ddb14SDimitry Andric return ScrollContext(height - 2, height - 1);
1662*5f7ddb14SDimitry Andric
1663*5f7ddb14SDimitry Andric FieldDelegate &field = m_fields[m_selection_index];
1664*5f7ddb14SDimitry Andric ScrollContext context = field.FieldDelegateGetScrollContext();
1665*5f7ddb14SDimitry Andric
1666*5f7ddb14SDimitry Andric // Start at 1 because of the top border.
1667*5f7ddb14SDimitry Andric int offset = 1;
1668*5f7ddb14SDimitry Andric for (int i = 0; i < m_selection_index; i++) {
1669*5f7ddb14SDimitry Andric offset += m_fields[i].FieldDelegateGetHeight();
1670*5f7ddb14SDimitry Andric }
1671*5f7ddb14SDimitry Andric context.Offset(offset);
1672*5f7ddb14SDimitry Andric
1673*5f7ddb14SDimitry Andric // If the scroll context is touching the top border, include it in the
1674*5f7ddb14SDimitry Andric // context to show the label.
1675*5f7ddb14SDimitry Andric if (context.start == 1)
1676*5f7ddb14SDimitry Andric context.start--;
1677*5f7ddb14SDimitry Andric
1678*5f7ddb14SDimitry Andric // If the scroll context is touching the new button, include it as well as
1679*5f7ddb14SDimitry Andric // the bottom border in the context.
1680*5f7ddb14SDimitry Andric if (context.end == height - 3)
1681*5f7ddb14SDimitry Andric context.end += 2;
1682*5f7ddb14SDimitry Andric
1683*5f7ddb14SDimitry Andric return context;
1684*5f7ddb14SDimitry Andric }
1685*5f7ddb14SDimitry Andric
DrawRemoveButton(SubPad & surface,int highlight)1686*5f7ddb14SDimitry Andric void DrawRemoveButton(SubPad &surface, int highlight) {
1687*5f7ddb14SDimitry Andric surface.MoveCursor(1, surface.GetHeight() / 2);
1688*5f7ddb14SDimitry Andric if (highlight)
1689*5f7ddb14SDimitry Andric surface.AttributeOn(A_REVERSE);
1690*5f7ddb14SDimitry Andric surface.PutCString("[Remove]");
1691*5f7ddb14SDimitry Andric if (highlight)
1692*5f7ddb14SDimitry Andric surface.AttributeOff(A_REVERSE);
1693*5f7ddb14SDimitry Andric }
1694*5f7ddb14SDimitry Andric
DrawFields(SubPad & surface,bool is_selected)1695*5f7ddb14SDimitry Andric void DrawFields(SubPad &surface, bool is_selected) {
1696*5f7ddb14SDimitry Andric int line = 0;
1697*5f7ddb14SDimitry Andric int width = surface.GetWidth();
1698*5f7ddb14SDimitry Andric for (int i = 0; i < GetNumberOfFields(); i++) {
1699*5f7ddb14SDimitry Andric int height = m_fields[i].FieldDelegateGetHeight();
1700*5f7ddb14SDimitry Andric Rect bounds = Rect(Point(0, line), Size(width, height));
1701*5f7ddb14SDimitry Andric Rect field_bounds, remove_button_bounds;
1702*5f7ddb14SDimitry Andric bounds.VerticalSplit(bounds.size.width - sizeof(" [Remove]"),
1703*5f7ddb14SDimitry Andric field_bounds, remove_button_bounds);
1704*5f7ddb14SDimitry Andric SubPad field_surface = SubPad(surface, field_bounds);
1705*5f7ddb14SDimitry Andric SubPad remove_button_surface = SubPad(surface, remove_button_bounds);
1706*5f7ddb14SDimitry Andric
1707*5f7ddb14SDimitry Andric bool is_element_selected = m_selection_index == i && is_selected;
1708*5f7ddb14SDimitry Andric bool is_field_selected =
1709*5f7ddb14SDimitry Andric is_element_selected && m_selection_type == SelectionType::Field;
1710*5f7ddb14SDimitry Andric bool is_remove_button_selected =
1711*5f7ddb14SDimitry Andric is_element_selected &&
1712*5f7ddb14SDimitry Andric m_selection_type == SelectionType::RemoveButton;
1713*5f7ddb14SDimitry Andric m_fields[i].FieldDelegateDraw(field_surface, is_field_selected);
1714*5f7ddb14SDimitry Andric DrawRemoveButton(remove_button_surface, is_remove_button_selected);
1715*5f7ddb14SDimitry Andric
1716*5f7ddb14SDimitry Andric line += height;
1717*5f7ddb14SDimitry Andric }
1718*5f7ddb14SDimitry Andric }
1719*5f7ddb14SDimitry Andric
DrawNewButton(SubPad & surface,bool is_selected)1720*5f7ddb14SDimitry Andric void DrawNewButton(SubPad &surface, bool is_selected) {
1721*5f7ddb14SDimitry Andric const char *button_text = "[New]";
1722*5f7ddb14SDimitry Andric int x = (surface.GetWidth() - sizeof(button_text) - 1) / 2;
1723*5f7ddb14SDimitry Andric surface.MoveCursor(x, 0);
1724*5f7ddb14SDimitry Andric bool highlight =
1725*5f7ddb14SDimitry Andric is_selected && m_selection_type == SelectionType::NewButton;
1726*5f7ddb14SDimitry Andric if (highlight)
1727*5f7ddb14SDimitry Andric surface.AttributeOn(A_REVERSE);
1728*5f7ddb14SDimitry Andric surface.PutCString(button_text);
1729*5f7ddb14SDimitry Andric if (highlight)
1730*5f7ddb14SDimitry Andric surface.AttributeOff(A_REVERSE);
1731*5f7ddb14SDimitry Andric }
1732*5f7ddb14SDimitry Andric
FieldDelegateDraw(SubPad & surface,bool is_selected)1733*5f7ddb14SDimitry Andric void FieldDelegateDraw(SubPad &surface, bool is_selected) override {
1734*5f7ddb14SDimitry Andric surface.TitledBox(m_label.c_str());
1735*5f7ddb14SDimitry Andric
1736*5f7ddb14SDimitry Andric Rect content_bounds = surface.GetFrame();
1737*5f7ddb14SDimitry Andric content_bounds.Inset(1, 1);
1738*5f7ddb14SDimitry Andric Rect fields_bounds, new_button_bounds;
1739*5f7ddb14SDimitry Andric content_bounds.HorizontalSplit(content_bounds.size.height - 1,
1740*5f7ddb14SDimitry Andric fields_bounds, new_button_bounds);
1741*5f7ddb14SDimitry Andric SubPad fields_surface = SubPad(surface, fields_bounds);
1742*5f7ddb14SDimitry Andric SubPad new_button_surface = SubPad(surface, new_button_bounds);
1743*5f7ddb14SDimitry Andric
1744*5f7ddb14SDimitry Andric DrawFields(fields_surface, is_selected);
1745*5f7ddb14SDimitry Andric DrawNewButton(new_button_surface, is_selected);
1746*5f7ddb14SDimitry Andric }
1747*5f7ddb14SDimitry Andric
AddNewField()1748*5f7ddb14SDimitry Andric void AddNewField() {
1749*5f7ddb14SDimitry Andric m_fields.push_back(m_default_field);
1750*5f7ddb14SDimitry Andric m_selection_index = GetNumberOfFields() - 1;
1751*5f7ddb14SDimitry Andric m_selection_type = SelectionType::Field;
1752*5f7ddb14SDimitry Andric FieldDelegate &field = m_fields[m_selection_index];
1753*5f7ddb14SDimitry Andric field.FieldDelegateSelectFirstElement();
1754*5f7ddb14SDimitry Andric }
1755*5f7ddb14SDimitry Andric
RemoveField()1756*5f7ddb14SDimitry Andric void RemoveField() {
1757*5f7ddb14SDimitry Andric m_fields.erase(m_fields.begin() + m_selection_index);
1758*5f7ddb14SDimitry Andric if (m_selection_index != 0)
1759*5f7ddb14SDimitry Andric m_selection_index--;
1760*5f7ddb14SDimitry Andric
1761*5f7ddb14SDimitry Andric if (GetNumberOfFields() > 0) {
1762*5f7ddb14SDimitry Andric m_selection_type = SelectionType::Field;
1763*5f7ddb14SDimitry Andric FieldDelegate &field = m_fields[m_selection_index];
1764*5f7ddb14SDimitry Andric field.FieldDelegateSelectFirstElement();
1765*5f7ddb14SDimitry Andric } else
1766*5f7ddb14SDimitry Andric m_selection_type = SelectionType::NewButton;
1767*5f7ddb14SDimitry Andric }
1768*5f7ddb14SDimitry Andric
SelectNext(int key)1769*5f7ddb14SDimitry Andric HandleCharResult SelectNext(int key) {
1770*5f7ddb14SDimitry Andric if (m_selection_type == SelectionType::NewButton)
1771*5f7ddb14SDimitry Andric return eKeyNotHandled;
1772*5f7ddb14SDimitry Andric
1773*5f7ddb14SDimitry Andric if (m_selection_type == SelectionType::RemoveButton) {
1774*5f7ddb14SDimitry Andric if (m_selection_index == GetNumberOfFields() - 1) {
1775*5f7ddb14SDimitry Andric m_selection_type = SelectionType::NewButton;
1776*5f7ddb14SDimitry Andric return eKeyHandled;
1777*5f7ddb14SDimitry Andric }
1778*5f7ddb14SDimitry Andric m_selection_index++;
1779*5f7ddb14SDimitry Andric m_selection_type = SelectionType::Field;
1780*5f7ddb14SDimitry Andric FieldDelegate &next_field = m_fields[m_selection_index];
1781*5f7ddb14SDimitry Andric next_field.FieldDelegateSelectFirstElement();
1782*5f7ddb14SDimitry Andric return eKeyHandled;
1783*5f7ddb14SDimitry Andric }
1784*5f7ddb14SDimitry Andric
1785*5f7ddb14SDimitry Andric FieldDelegate &field = m_fields[m_selection_index];
1786*5f7ddb14SDimitry Andric if (!field.FieldDelegateOnLastOrOnlyElement()) {
1787*5f7ddb14SDimitry Andric return field.FieldDelegateHandleChar(key);
1788*5f7ddb14SDimitry Andric }
1789*5f7ddb14SDimitry Andric
1790*5f7ddb14SDimitry Andric field.FieldDelegateExitCallback();
1791*5f7ddb14SDimitry Andric
1792*5f7ddb14SDimitry Andric m_selection_type = SelectionType::RemoveButton;
1793*5f7ddb14SDimitry Andric return eKeyHandled;
1794*5f7ddb14SDimitry Andric }
1795*5f7ddb14SDimitry Andric
SelectPrevious(int key)1796*5f7ddb14SDimitry Andric HandleCharResult SelectPrevious(int key) {
1797*5f7ddb14SDimitry Andric if (FieldDelegateOnFirstOrOnlyElement())
1798*5f7ddb14SDimitry Andric return eKeyNotHandled;
1799*5f7ddb14SDimitry Andric
1800*5f7ddb14SDimitry Andric if (m_selection_type == SelectionType::RemoveButton) {
1801*5f7ddb14SDimitry Andric m_selection_type = SelectionType::Field;
1802*5f7ddb14SDimitry Andric FieldDelegate &field = m_fields[m_selection_index];
1803*5f7ddb14SDimitry Andric field.FieldDelegateSelectLastElement();
1804*5f7ddb14SDimitry Andric return eKeyHandled;
1805*5f7ddb14SDimitry Andric }
1806*5f7ddb14SDimitry Andric
1807*5f7ddb14SDimitry Andric if (m_selection_type == SelectionType::NewButton) {
1808*5f7ddb14SDimitry Andric m_selection_type = SelectionType::RemoveButton;
1809*5f7ddb14SDimitry Andric m_selection_index = GetNumberOfFields() - 1;
1810*5f7ddb14SDimitry Andric return eKeyHandled;
1811*5f7ddb14SDimitry Andric }
1812*5f7ddb14SDimitry Andric
1813*5f7ddb14SDimitry Andric FieldDelegate &field = m_fields[m_selection_index];
1814*5f7ddb14SDimitry Andric if (!field.FieldDelegateOnFirstOrOnlyElement()) {
1815*5f7ddb14SDimitry Andric return field.FieldDelegateHandleChar(key);
1816*5f7ddb14SDimitry Andric }
1817*5f7ddb14SDimitry Andric
1818*5f7ddb14SDimitry Andric field.FieldDelegateExitCallback();
1819*5f7ddb14SDimitry Andric
1820*5f7ddb14SDimitry Andric m_selection_type = SelectionType::RemoveButton;
1821*5f7ddb14SDimitry Andric m_selection_index--;
1822*5f7ddb14SDimitry Andric return eKeyHandled;
1823*5f7ddb14SDimitry Andric }
1824*5f7ddb14SDimitry Andric
FieldDelegateHandleChar(int key)1825*5f7ddb14SDimitry Andric HandleCharResult FieldDelegateHandleChar(int key) override {
1826*5f7ddb14SDimitry Andric switch (key) {
1827*5f7ddb14SDimitry Andric case '\r':
1828*5f7ddb14SDimitry Andric case '\n':
1829*5f7ddb14SDimitry Andric case KEY_ENTER:
1830*5f7ddb14SDimitry Andric switch (m_selection_type) {
1831*5f7ddb14SDimitry Andric case SelectionType::NewButton:
1832*5f7ddb14SDimitry Andric AddNewField();
1833*5f7ddb14SDimitry Andric return eKeyHandled;
1834*5f7ddb14SDimitry Andric case SelectionType::RemoveButton:
1835*5f7ddb14SDimitry Andric RemoveField();
1836*5f7ddb14SDimitry Andric return eKeyHandled;
1837*5f7ddb14SDimitry Andric default:
1838*5f7ddb14SDimitry Andric break;
1839*5f7ddb14SDimitry Andric }
1840*5f7ddb14SDimitry Andric break;
1841*5f7ddb14SDimitry Andric case '\t':
1842*5f7ddb14SDimitry Andric SelectNext(key);
1843*5f7ddb14SDimitry Andric return eKeyHandled;
1844*5f7ddb14SDimitry Andric case KEY_SHIFT_TAB:
1845*5f7ddb14SDimitry Andric SelectPrevious(key);
1846*5f7ddb14SDimitry Andric return eKeyHandled;
1847*5f7ddb14SDimitry Andric default:
1848*5f7ddb14SDimitry Andric break;
1849*5f7ddb14SDimitry Andric }
1850*5f7ddb14SDimitry Andric
1851*5f7ddb14SDimitry Andric // If the key wasn't handled and one of the fields is selected, pass the key
1852*5f7ddb14SDimitry Andric // to that field.
1853*5f7ddb14SDimitry Andric if (m_selection_type == SelectionType::Field) {
1854*5f7ddb14SDimitry Andric return m_fields[m_selection_index].FieldDelegateHandleChar(key);
1855*5f7ddb14SDimitry Andric }
1856*5f7ddb14SDimitry Andric
1857*5f7ddb14SDimitry Andric return eKeyNotHandled;
1858*5f7ddb14SDimitry Andric }
1859*5f7ddb14SDimitry Andric
FieldDelegateOnLastOrOnlyElement()1860*5f7ddb14SDimitry Andric bool FieldDelegateOnLastOrOnlyElement() override {
1861*5f7ddb14SDimitry Andric if (m_selection_type == SelectionType::NewButton) {
1862*5f7ddb14SDimitry Andric return true;
1863*5f7ddb14SDimitry Andric }
1864*5f7ddb14SDimitry Andric return false;
1865*5f7ddb14SDimitry Andric }
1866*5f7ddb14SDimitry Andric
FieldDelegateOnFirstOrOnlyElement()1867*5f7ddb14SDimitry Andric bool FieldDelegateOnFirstOrOnlyElement() override {
1868*5f7ddb14SDimitry Andric if (m_selection_type == SelectionType::NewButton &&
1869*5f7ddb14SDimitry Andric GetNumberOfFields() == 0)
1870*5f7ddb14SDimitry Andric return true;
1871*5f7ddb14SDimitry Andric
1872*5f7ddb14SDimitry Andric if (m_selection_type == SelectionType::Field && m_selection_index == 0) {
1873*5f7ddb14SDimitry Andric FieldDelegate &field = m_fields[m_selection_index];
1874*5f7ddb14SDimitry Andric return field.FieldDelegateOnFirstOrOnlyElement();
1875*5f7ddb14SDimitry Andric }
1876*5f7ddb14SDimitry Andric
1877*5f7ddb14SDimitry Andric return false;
1878*5f7ddb14SDimitry Andric }
1879*5f7ddb14SDimitry Andric
FieldDelegateSelectFirstElement()1880*5f7ddb14SDimitry Andric void FieldDelegateSelectFirstElement() override {
1881*5f7ddb14SDimitry Andric if (GetNumberOfFields() == 0) {
1882*5f7ddb14SDimitry Andric m_selection_type = SelectionType::NewButton;
1883*5f7ddb14SDimitry Andric return;
1884*5f7ddb14SDimitry Andric }
1885*5f7ddb14SDimitry Andric
1886*5f7ddb14SDimitry Andric m_selection_type = SelectionType::Field;
1887*5f7ddb14SDimitry Andric m_selection_index = 0;
1888*5f7ddb14SDimitry Andric }
1889*5f7ddb14SDimitry Andric
FieldDelegateSelectLastElement()1890*5f7ddb14SDimitry Andric void FieldDelegateSelectLastElement() override {
1891*5f7ddb14SDimitry Andric m_selection_type = SelectionType::NewButton;
1892*5f7ddb14SDimitry Andric return;
1893*5f7ddb14SDimitry Andric }
1894*5f7ddb14SDimitry Andric
GetNumberOfFields()1895*5f7ddb14SDimitry Andric int GetNumberOfFields() { return m_fields.size(); }
1896*5f7ddb14SDimitry Andric
1897*5f7ddb14SDimitry Andric // Returns the form delegate at the current index.
GetField(int index)1898*5f7ddb14SDimitry Andric T &GetField(int index) { return m_fields[index]; }
1899*5f7ddb14SDimitry Andric
1900*5f7ddb14SDimitry Andric protected:
1901*5f7ddb14SDimitry Andric std::string m_label;
1902*5f7ddb14SDimitry Andric // The default field delegate instance from which new field delegates will be
1903*5f7ddb14SDimitry Andric // created though a copy.
1904*5f7ddb14SDimitry Andric T m_default_field;
1905*5f7ddb14SDimitry Andric std::vector<T> m_fields;
1906*5f7ddb14SDimitry Andric int m_selection_index;
1907*5f7ddb14SDimitry Andric // See SelectionType class enum.
1908*5f7ddb14SDimitry Andric SelectionType m_selection_type;
1909*5f7ddb14SDimitry Andric };
1910*5f7ddb14SDimitry Andric
1911*5f7ddb14SDimitry Andric class FormAction {
1912*5f7ddb14SDimitry Andric public:
FormAction(const char * label,std::function<void (Window &)> action)1913*5f7ddb14SDimitry Andric FormAction(const char *label, std::function<void(Window &)> action)
1914*5f7ddb14SDimitry Andric : m_action(action) {
1915*5f7ddb14SDimitry Andric if (label)
1916*5f7ddb14SDimitry Andric m_label = label;
1917*5f7ddb14SDimitry Andric }
1918*5f7ddb14SDimitry Andric
1919*5f7ddb14SDimitry Andric // Draw a centered [Label].
Draw(SubPad & surface,bool is_selected)1920*5f7ddb14SDimitry Andric void Draw(SubPad &surface, bool is_selected) {
1921*5f7ddb14SDimitry Andric int x = (surface.GetWidth() - m_label.length()) / 2;
1922*5f7ddb14SDimitry Andric surface.MoveCursor(x, 0);
1923*5f7ddb14SDimitry Andric if (is_selected)
1924*5f7ddb14SDimitry Andric surface.AttributeOn(A_REVERSE);
1925*5f7ddb14SDimitry Andric surface.PutChar('[');
1926*5f7ddb14SDimitry Andric surface.PutCString(m_label.c_str());
1927*5f7ddb14SDimitry Andric surface.PutChar(']');
1928*5f7ddb14SDimitry Andric if (is_selected)
1929*5f7ddb14SDimitry Andric surface.AttributeOff(A_REVERSE);
1930*5f7ddb14SDimitry Andric }
1931*5f7ddb14SDimitry Andric
Execute(Window & window)1932*5f7ddb14SDimitry Andric void Execute(Window &window) { m_action(window); }
1933*5f7ddb14SDimitry Andric
GetLabel()1934*5f7ddb14SDimitry Andric const std::string &GetLabel() { return m_label; }
1935*5f7ddb14SDimitry Andric
1936*5f7ddb14SDimitry Andric protected:
1937*5f7ddb14SDimitry Andric std::string m_label;
1938*5f7ddb14SDimitry Andric std::function<void(Window &)> m_action;
1939*5f7ddb14SDimitry Andric };
1940*5f7ddb14SDimitry Andric
1941*5f7ddb14SDimitry Andric class FormDelegate {
1942*5f7ddb14SDimitry Andric public:
FormDelegate()1943*5f7ddb14SDimitry Andric FormDelegate() {}
1944*5f7ddb14SDimitry Andric
1945*5f7ddb14SDimitry Andric virtual ~FormDelegate() = default;
1946*5f7ddb14SDimitry Andric
1947*5f7ddb14SDimitry Andric virtual std::string GetName() = 0;
1948*5f7ddb14SDimitry Andric
UpdateFieldsVisibility()1949*5f7ddb14SDimitry Andric virtual void UpdateFieldsVisibility() { return; }
1950*5f7ddb14SDimitry Andric
GetField(uint32_t field_index)1951*5f7ddb14SDimitry Andric FieldDelegate *GetField(uint32_t field_index) {
1952*5f7ddb14SDimitry Andric if (field_index < m_fields.size())
1953*5f7ddb14SDimitry Andric return m_fields[field_index].get();
1954*5f7ddb14SDimitry Andric return nullptr;
1955*5f7ddb14SDimitry Andric }
1956*5f7ddb14SDimitry Andric
GetAction(int action_index)1957*5f7ddb14SDimitry Andric FormAction &GetAction(int action_index) { return m_actions[action_index]; }
1958*5f7ddb14SDimitry Andric
GetNumberOfFields()1959*5f7ddb14SDimitry Andric int GetNumberOfFields() { return m_fields.size(); }
1960*5f7ddb14SDimitry Andric
GetNumberOfActions()1961*5f7ddb14SDimitry Andric int GetNumberOfActions() { return m_actions.size(); }
1962*5f7ddb14SDimitry Andric
HasError()1963*5f7ddb14SDimitry Andric bool HasError() { return !m_error.empty(); }
1964*5f7ddb14SDimitry Andric
ClearError()1965*5f7ddb14SDimitry Andric void ClearError() { m_error.clear(); }
1966*5f7ddb14SDimitry Andric
GetError()1967*5f7ddb14SDimitry Andric const std::string &GetError() { return m_error; }
1968*5f7ddb14SDimitry Andric
SetError(const char * error)1969*5f7ddb14SDimitry Andric void SetError(const char *error) { m_error = error; }
1970*5f7ddb14SDimitry Andric
1971*5f7ddb14SDimitry Andric // If all fields are valid, true is returned. Otherwise, an error message is
1972*5f7ddb14SDimitry Andric // set and false is returned. This method is usually called at the start of an
1973*5f7ddb14SDimitry Andric // action that requires valid fields.
CheckFieldsValidity()1974*5f7ddb14SDimitry Andric bool CheckFieldsValidity() {
1975*5f7ddb14SDimitry Andric for (int i = 0; i < GetNumberOfFields(); i++) {
1976*5f7ddb14SDimitry Andric if (GetField(i)->FieldDelegateHasError()) {
1977*5f7ddb14SDimitry Andric SetError("Some fields are invalid!");
1978*5f7ddb14SDimitry Andric return false;
1979*5f7ddb14SDimitry Andric }
1980*5f7ddb14SDimitry Andric }
1981*5f7ddb14SDimitry Andric return true;
1982*5f7ddb14SDimitry Andric }
1983*5f7ddb14SDimitry Andric
1984*5f7ddb14SDimitry Andric // Factory methods to create and add fields of specific types.
1985*5f7ddb14SDimitry Andric
AddTextField(const char * label,const char * content,bool required)1986*5f7ddb14SDimitry Andric TextFieldDelegate *AddTextField(const char *label, const char *content,
1987*5f7ddb14SDimitry Andric bool required) {
1988*5f7ddb14SDimitry Andric TextFieldDelegate *delegate =
1989*5f7ddb14SDimitry Andric new TextFieldDelegate(label, content, required);
1990*5f7ddb14SDimitry Andric m_fields.push_back(FieldDelegateUP(delegate));
1991*5f7ddb14SDimitry Andric return delegate;
1992*5f7ddb14SDimitry Andric }
1993*5f7ddb14SDimitry Andric
AddFileField(const char * label,const char * content,bool need_to_exist,bool required)1994*5f7ddb14SDimitry Andric FileFieldDelegate *AddFileField(const char *label, const char *content,
1995*5f7ddb14SDimitry Andric bool need_to_exist, bool required) {
1996*5f7ddb14SDimitry Andric FileFieldDelegate *delegate =
1997*5f7ddb14SDimitry Andric new FileFieldDelegate(label, content, need_to_exist, required);
1998*5f7ddb14SDimitry Andric m_fields.push_back(FieldDelegateUP(delegate));
1999*5f7ddb14SDimitry Andric return delegate;
2000*5f7ddb14SDimitry Andric }
2001*5f7ddb14SDimitry Andric
AddDirectoryField(const char * label,const char * content,bool need_to_exist,bool required)2002*5f7ddb14SDimitry Andric DirectoryFieldDelegate *AddDirectoryField(const char *label,
2003*5f7ddb14SDimitry Andric const char *content,
2004*5f7ddb14SDimitry Andric bool need_to_exist, bool required) {
2005*5f7ddb14SDimitry Andric DirectoryFieldDelegate *delegate =
2006*5f7ddb14SDimitry Andric new DirectoryFieldDelegate(label, content, need_to_exist, required);
2007*5f7ddb14SDimitry Andric m_fields.push_back(FieldDelegateUP(delegate));
2008*5f7ddb14SDimitry Andric return delegate;
2009*5f7ddb14SDimitry Andric }
2010*5f7ddb14SDimitry Andric
AddArchField(const char * label,const char * content,bool required)2011*5f7ddb14SDimitry Andric ArchFieldDelegate *AddArchField(const char *label, const char *content,
2012*5f7ddb14SDimitry Andric bool required) {
2013*5f7ddb14SDimitry Andric ArchFieldDelegate *delegate =
2014*5f7ddb14SDimitry Andric new ArchFieldDelegate(label, content, required);
2015*5f7ddb14SDimitry Andric m_fields.push_back(FieldDelegateUP(delegate));
2016*5f7ddb14SDimitry Andric return delegate;
2017*5f7ddb14SDimitry Andric }
2018*5f7ddb14SDimitry Andric
AddIntegerField(const char * label,int content,bool required)2019*5f7ddb14SDimitry Andric IntegerFieldDelegate *AddIntegerField(const char *label, int content,
2020*5f7ddb14SDimitry Andric bool required) {
2021*5f7ddb14SDimitry Andric IntegerFieldDelegate *delegate =
2022*5f7ddb14SDimitry Andric new IntegerFieldDelegate(label, content, required);
2023*5f7ddb14SDimitry Andric m_fields.push_back(FieldDelegateUP(delegate));
2024*5f7ddb14SDimitry Andric return delegate;
2025*5f7ddb14SDimitry Andric }
2026*5f7ddb14SDimitry Andric
AddBooleanField(const char * label,bool content)2027*5f7ddb14SDimitry Andric BooleanFieldDelegate *AddBooleanField(const char *label, bool content) {
2028*5f7ddb14SDimitry Andric BooleanFieldDelegate *delegate = new BooleanFieldDelegate(label, content);
2029*5f7ddb14SDimitry Andric m_fields.push_back(FieldDelegateUP(delegate));
2030*5f7ddb14SDimitry Andric return delegate;
2031*5f7ddb14SDimitry Andric }
2032*5f7ddb14SDimitry Andric
AddChoicesField(const char * label,int height,std::vector<std::string> choices)2033*5f7ddb14SDimitry Andric ChoicesFieldDelegate *AddChoicesField(const char *label, int height,
2034*5f7ddb14SDimitry Andric std::vector<std::string> choices) {
2035*5f7ddb14SDimitry Andric ChoicesFieldDelegate *delegate =
2036*5f7ddb14SDimitry Andric new ChoicesFieldDelegate(label, height, choices);
2037*5f7ddb14SDimitry Andric m_fields.push_back(FieldDelegateUP(delegate));
2038*5f7ddb14SDimitry Andric return delegate;
2039*5f7ddb14SDimitry Andric }
2040*5f7ddb14SDimitry Andric
AddPlatformPluginField(Debugger & debugger)2041*5f7ddb14SDimitry Andric PlatformPluginFieldDelegate *AddPlatformPluginField(Debugger &debugger) {
2042*5f7ddb14SDimitry Andric PlatformPluginFieldDelegate *delegate =
2043*5f7ddb14SDimitry Andric new PlatformPluginFieldDelegate(debugger);
2044*5f7ddb14SDimitry Andric m_fields.push_back(FieldDelegateUP(delegate));
2045*5f7ddb14SDimitry Andric return delegate;
2046*5f7ddb14SDimitry Andric }
2047*5f7ddb14SDimitry Andric
AddProcessPluginField()2048*5f7ddb14SDimitry Andric ProcessPluginFieldDelegate *AddProcessPluginField() {
2049*5f7ddb14SDimitry Andric ProcessPluginFieldDelegate *delegate = new ProcessPluginFieldDelegate();
2050*5f7ddb14SDimitry Andric m_fields.push_back(FieldDelegateUP(delegate));
2051*5f7ddb14SDimitry Andric return delegate;
2052*5f7ddb14SDimitry Andric }
2053*5f7ddb14SDimitry Andric
2054*5f7ddb14SDimitry Andric template <class T>
AddListField(const char * label,T default_field)2055*5f7ddb14SDimitry Andric ListFieldDelegate<T> *AddListField(const char *label, T default_field) {
2056*5f7ddb14SDimitry Andric ListFieldDelegate<T> *delegate =
2057*5f7ddb14SDimitry Andric new ListFieldDelegate<T>(label, default_field);
2058*5f7ddb14SDimitry Andric m_fields.push_back(FieldDelegateUP(delegate));
2059*5f7ddb14SDimitry Andric return delegate;
2060*5f7ddb14SDimitry Andric }
2061*5f7ddb14SDimitry Andric
2062*5f7ddb14SDimitry Andric // Factory methods for adding actions.
2063*5f7ddb14SDimitry Andric
AddAction(const char * label,std::function<void (Window &)> action)2064*5f7ddb14SDimitry Andric void AddAction(const char *label, std::function<void(Window &)> action) {
2065*5f7ddb14SDimitry Andric m_actions.push_back(FormAction(label, action));
2066*5f7ddb14SDimitry Andric }
2067*5f7ddb14SDimitry Andric
2068*5f7ddb14SDimitry Andric protected:
2069*5f7ddb14SDimitry Andric std::vector<FieldDelegateUP> m_fields;
2070*5f7ddb14SDimitry Andric std::vector<FormAction> m_actions;
2071*5f7ddb14SDimitry Andric // Optional error message. If empty, form is considered to have no error.
2072*5f7ddb14SDimitry Andric std::string m_error;
2073*5f7ddb14SDimitry Andric };
2074*5f7ddb14SDimitry Andric
2075*5f7ddb14SDimitry Andric typedef std::shared_ptr<FormDelegate> FormDelegateSP;
2076*5f7ddb14SDimitry Andric
2077*5f7ddb14SDimitry Andric class FormWindowDelegate : public WindowDelegate {
2078*5f7ddb14SDimitry Andric public:
FormWindowDelegate(FormDelegateSP & delegate_sp)2079*5f7ddb14SDimitry Andric FormWindowDelegate(FormDelegateSP &delegate_sp)
2080*5f7ddb14SDimitry Andric : m_delegate_sp(delegate_sp), m_selection_index(0),
2081*5f7ddb14SDimitry Andric m_first_visible_line(0) {
2082*5f7ddb14SDimitry Andric assert(m_delegate_sp->GetNumberOfActions() > 0);
2083*5f7ddb14SDimitry Andric if (m_delegate_sp->GetNumberOfFields() > 0)
2084*5f7ddb14SDimitry Andric m_selection_type = SelectionType::Field;
2085*5f7ddb14SDimitry Andric else
2086*5f7ddb14SDimitry Andric m_selection_type = SelectionType::Action;
2087*5f7ddb14SDimitry Andric }
2088*5f7ddb14SDimitry Andric
2089*5f7ddb14SDimitry Andric // Signify which element is selected. If a field or an action is selected,
2090*5f7ddb14SDimitry Andric // then m_selection_index signifies the particular field or action that is
2091*5f7ddb14SDimitry Andric // selected.
2092*5f7ddb14SDimitry Andric enum class SelectionType { Field, Action };
2093*5f7ddb14SDimitry Andric
2094*5f7ddb14SDimitry Andric // A form window is padded by one character from all sides. First, if an error
2095*5f7ddb14SDimitry Andric // message exists, it is drawn followed by a separator. Then one or more
2096*5f7ddb14SDimitry Andric // fields are drawn. Finally, all available actions are drawn on a single
2097*5f7ddb14SDimitry Andric // line.
2098*5f7ddb14SDimitry Andric //
2099*5f7ddb14SDimitry Andric // ___<Form Name>_________________________________________________
2100*5f7ddb14SDimitry Andric // | |
2101*5f7ddb14SDimitry Andric // | - Error message if it exists. |
2102*5f7ddb14SDimitry Andric // |-------------------------------------------------------------|
2103*5f7ddb14SDimitry Andric // | Form elements here. |
2104*5f7ddb14SDimitry Andric // | Form actions here. |
2105*5f7ddb14SDimitry Andric // | |
2106*5f7ddb14SDimitry Andric // |______________________________________[Press Esc to cancel]__|
2107*5f7ddb14SDimitry Andric //
2108*5f7ddb14SDimitry Andric
2109*5f7ddb14SDimitry Andric // One line for the error and another for the horizontal line.
GetErrorHeight()2110*5f7ddb14SDimitry Andric int GetErrorHeight() {
2111*5f7ddb14SDimitry Andric if (m_delegate_sp->HasError())
2112*5f7ddb14SDimitry Andric return 2;
2113*5f7ddb14SDimitry Andric return 0;
2114*5f7ddb14SDimitry Andric }
2115*5f7ddb14SDimitry Andric
2116*5f7ddb14SDimitry Andric // Actions span a single line.
GetActionsHeight()2117*5f7ddb14SDimitry Andric int GetActionsHeight() {
2118*5f7ddb14SDimitry Andric if (m_delegate_sp->GetNumberOfActions() > 0)
2119*5f7ddb14SDimitry Andric return 1;
2120*5f7ddb14SDimitry Andric return 0;
2121*5f7ddb14SDimitry Andric }
2122*5f7ddb14SDimitry Andric
2123*5f7ddb14SDimitry Andric // Get the total number of needed lines to draw the contents.
GetContentHeight()2124*5f7ddb14SDimitry Andric int GetContentHeight() {
2125*5f7ddb14SDimitry Andric int height = 0;
2126*5f7ddb14SDimitry Andric height += GetErrorHeight();
2127*5f7ddb14SDimitry Andric for (int i = 0; i < m_delegate_sp->GetNumberOfFields(); i++) {
2128*5f7ddb14SDimitry Andric if (!m_delegate_sp->GetField(i)->FieldDelegateIsVisible())
2129*5f7ddb14SDimitry Andric continue;
2130*5f7ddb14SDimitry Andric height += m_delegate_sp->GetField(i)->FieldDelegateGetHeight();
2131*5f7ddb14SDimitry Andric }
2132*5f7ddb14SDimitry Andric height += GetActionsHeight();
2133*5f7ddb14SDimitry Andric return height;
2134*5f7ddb14SDimitry Andric }
2135*5f7ddb14SDimitry Andric
GetScrollContext()2136*5f7ddb14SDimitry Andric ScrollContext GetScrollContext() {
2137*5f7ddb14SDimitry Andric if (m_selection_type == SelectionType::Action)
2138*5f7ddb14SDimitry Andric return ScrollContext(GetContentHeight() - 1);
2139*5f7ddb14SDimitry Andric
2140*5f7ddb14SDimitry Andric FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);
2141*5f7ddb14SDimitry Andric ScrollContext context = field->FieldDelegateGetScrollContext();
2142*5f7ddb14SDimitry Andric
2143*5f7ddb14SDimitry Andric int offset = GetErrorHeight();
2144*5f7ddb14SDimitry Andric for (int i = 0; i < m_selection_index; i++) {
2145*5f7ddb14SDimitry Andric if (!m_delegate_sp->GetField(i)->FieldDelegateIsVisible())
2146*5f7ddb14SDimitry Andric continue;
2147*5f7ddb14SDimitry Andric offset += m_delegate_sp->GetField(i)->FieldDelegateGetHeight();
2148*5f7ddb14SDimitry Andric }
2149*5f7ddb14SDimitry Andric context.Offset(offset);
2150*5f7ddb14SDimitry Andric
2151*5f7ddb14SDimitry Andric // If the context is touching the error, include the error in the context as
2152*5f7ddb14SDimitry Andric // well.
2153*5f7ddb14SDimitry Andric if (context.start == GetErrorHeight())
2154*5f7ddb14SDimitry Andric context.start = 0;
2155*5f7ddb14SDimitry Andric
2156*5f7ddb14SDimitry Andric return context;
2157*5f7ddb14SDimitry Andric }
2158*5f7ddb14SDimitry Andric
UpdateScrolling(DerivedWindow & surface)2159*5f7ddb14SDimitry Andric void UpdateScrolling(DerivedWindow &surface) {
2160*5f7ddb14SDimitry Andric ScrollContext context = GetScrollContext();
2161*5f7ddb14SDimitry Andric int content_height = GetContentHeight();
2162*5f7ddb14SDimitry Andric int surface_height = surface.GetHeight();
2163*5f7ddb14SDimitry Andric int visible_height = std::min(content_height, surface_height);
2164*5f7ddb14SDimitry Andric int last_visible_line = m_first_visible_line + visible_height - 1;
2165*5f7ddb14SDimitry Andric
2166*5f7ddb14SDimitry Andric // If the last visible line is bigger than the content, then it is invalid
2167*5f7ddb14SDimitry Andric // and needs to be set to the last line in the content. This can happen when
2168*5f7ddb14SDimitry Andric // a field has shrunk in height.
2169*5f7ddb14SDimitry Andric if (last_visible_line > content_height - 1) {
2170*5f7ddb14SDimitry Andric m_first_visible_line = content_height - visible_height;
2171*5f7ddb14SDimitry Andric }
2172*5f7ddb14SDimitry Andric
2173*5f7ddb14SDimitry Andric if (context.start < m_first_visible_line) {
2174*5f7ddb14SDimitry Andric m_first_visible_line = context.start;
2175*5f7ddb14SDimitry Andric return;
2176*5f7ddb14SDimitry Andric }
2177*5f7ddb14SDimitry Andric
2178*5f7ddb14SDimitry Andric if (context.end > last_visible_line) {
2179*5f7ddb14SDimitry Andric m_first_visible_line = context.end - visible_height + 1;
2180*5f7ddb14SDimitry Andric }
2181*5f7ddb14SDimitry Andric }
2182*5f7ddb14SDimitry Andric
DrawError(SubPad & surface)2183*5f7ddb14SDimitry Andric void DrawError(SubPad &surface) {
2184*5f7ddb14SDimitry Andric if (!m_delegate_sp->HasError())
2185*5f7ddb14SDimitry Andric return;
2186*5f7ddb14SDimitry Andric surface.MoveCursor(0, 0);
2187*5f7ddb14SDimitry Andric surface.AttributeOn(COLOR_PAIR(RedOnBlack));
2188*5f7ddb14SDimitry Andric surface.PutChar(ACS_DIAMOND);
2189*5f7ddb14SDimitry Andric surface.PutChar(' ');
2190*5f7ddb14SDimitry Andric surface.PutCStringTruncated(1, m_delegate_sp->GetError().c_str());
2191*5f7ddb14SDimitry Andric surface.AttributeOff(COLOR_PAIR(RedOnBlack));
2192*5f7ddb14SDimitry Andric
2193*5f7ddb14SDimitry Andric surface.MoveCursor(0, 1);
2194*5f7ddb14SDimitry Andric surface.HorizontalLine(surface.GetWidth());
2195*5f7ddb14SDimitry Andric }
2196*5f7ddb14SDimitry Andric
DrawFields(SubPad & surface)2197*5f7ddb14SDimitry Andric void DrawFields(SubPad &surface) {
2198*5f7ddb14SDimitry Andric int line = 0;
2199*5f7ddb14SDimitry Andric int width = surface.GetWidth();
2200*5f7ddb14SDimitry Andric bool a_field_is_selected = m_selection_type == SelectionType::Field;
2201*5f7ddb14SDimitry Andric for (int i = 0; i < m_delegate_sp->GetNumberOfFields(); i++) {
2202*5f7ddb14SDimitry Andric FieldDelegate *field = m_delegate_sp->GetField(i);
2203*5f7ddb14SDimitry Andric if (!field->FieldDelegateIsVisible())
2204*5f7ddb14SDimitry Andric continue;
2205*5f7ddb14SDimitry Andric bool is_field_selected = a_field_is_selected && m_selection_index == i;
2206*5f7ddb14SDimitry Andric int height = field->FieldDelegateGetHeight();
2207*5f7ddb14SDimitry Andric Rect bounds = Rect(Point(0, line), Size(width, height));
2208*5f7ddb14SDimitry Andric SubPad field_surface = SubPad(surface, bounds);
2209*5f7ddb14SDimitry Andric field->FieldDelegateDraw(field_surface, is_field_selected);
2210*5f7ddb14SDimitry Andric line += height;
2211*5f7ddb14SDimitry Andric }
2212*5f7ddb14SDimitry Andric }
2213*5f7ddb14SDimitry Andric
DrawActions(SubPad & surface)2214*5f7ddb14SDimitry Andric void DrawActions(SubPad &surface) {
2215*5f7ddb14SDimitry Andric int number_of_actions = m_delegate_sp->GetNumberOfActions();
2216*5f7ddb14SDimitry Andric int width = surface.GetWidth() / number_of_actions;
2217*5f7ddb14SDimitry Andric bool an_action_is_selected = m_selection_type == SelectionType::Action;
2218*5f7ddb14SDimitry Andric int x = 0;
2219*5f7ddb14SDimitry Andric for (int i = 0; i < number_of_actions; i++) {
2220*5f7ddb14SDimitry Andric bool is_action_selected = an_action_is_selected && m_selection_index == i;
2221*5f7ddb14SDimitry Andric FormAction &action = m_delegate_sp->GetAction(i);
2222*5f7ddb14SDimitry Andric Rect bounds = Rect(Point(x, 0), Size(width, 1));
2223*5f7ddb14SDimitry Andric SubPad action_surface = SubPad(surface, bounds);
2224*5f7ddb14SDimitry Andric action.Draw(action_surface, is_action_selected);
2225*5f7ddb14SDimitry Andric x += width;
2226*5f7ddb14SDimitry Andric }
2227*5f7ddb14SDimitry Andric }
2228*5f7ddb14SDimitry Andric
DrawElements(SubPad & surface)2229*5f7ddb14SDimitry Andric void DrawElements(SubPad &surface) {
2230*5f7ddb14SDimitry Andric Rect frame = surface.GetFrame();
2231*5f7ddb14SDimitry Andric Rect fields_bounds, actions_bounds;
2232*5f7ddb14SDimitry Andric frame.HorizontalSplit(surface.GetHeight() - GetActionsHeight(),
2233*5f7ddb14SDimitry Andric fields_bounds, actions_bounds);
2234*5f7ddb14SDimitry Andric SubPad fields_surface = SubPad(surface, fields_bounds);
2235*5f7ddb14SDimitry Andric SubPad actions_surface = SubPad(surface, actions_bounds);
2236*5f7ddb14SDimitry Andric
2237*5f7ddb14SDimitry Andric DrawFields(fields_surface);
2238*5f7ddb14SDimitry Andric DrawActions(actions_surface);
2239*5f7ddb14SDimitry Andric }
2240*5f7ddb14SDimitry Andric
2241*5f7ddb14SDimitry Andric // Contents are first drawn on a pad. Then a subset of that pad is copied to
2242*5f7ddb14SDimitry Andric // the derived window starting at the first visible line. This essentially
2243*5f7ddb14SDimitry Andric // provides scrolling functionality.
DrawContent(DerivedWindow & surface)2244*5f7ddb14SDimitry Andric void DrawContent(DerivedWindow &surface) {
2245*5f7ddb14SDimitry Andric UpdateScrolling(surface);
2246*5f7ddb14SDimitry Andric
2247*5f7ddb14SDimitry Andric int width = surface.GetWidth();
2248*5f7ddb14SDimitry Andric int height = GetContentHeight();
2249*5f7ddb14SDimitry Andric Pad pad = Pad(Size(width, height));
2250*5f7ddb14SDimitry Andric
2251*5f7ddb14SDimitry Andric Rect frame = pad.GetFrame();
2252*5f7ddb14SDimitry Andric Rect error_bounds, elements_bounds;
2253*5f7ddb14SDimitry Andric frame.HorizontalSplit(GetErrorHeight(), error_bounds, elements_bounds);
2254*5f7ddb14SDimitry Andric SubPad error_surface = SubPad(pad, error_bounds);
2255*5f7ddb14SDimitry Andric SubPad elements_surface = SubPad(pad, elements_bounds);
2256*5f7ddb14SDimitry Andric
2257*5f7ddb14SDimitry Andric DrawError(error_surface);
2258*5f7ddb14SDimitry Andric DrawElements(elements_surface);
2259*5f7ddb14SDimitry Andric
2260*5f7ddb14SDimitry Andric int copy_height = std::min(surface.GetHeight(), pad.GetHeight());
2261*5f7ddb14SDimitry Andric pad.CopyToSurface(surface, Point(0, m_first_visible_line), Point(),
2262*5f7ddb14SDimitry Andric Size(width, copy_height));
2263*5f7ddb14SDimitry Andric }
2264*5f7ddb14SDimitry Andric
WindowDelegateDraw(Window & window,bool force)2265*5f7ddb14SDimitry Andric bool WindowDelegateDraw(Window &window, bool force) override {
2266*5f7ddb14SDimitry Andric m_delegate_sp->UpdateFieldsVisibility();
2267*5f7ddb14SDimitry Andric
2268*5f7ddb14SDimitry Andric window.Erase();
2269*5f7ddb14SDimitry Andric
2270*5f7ddb14SDimitry Andric window.DrawTitleBox(m_delegate_sp->GetName().c_str(),
2271*5f7ddb14SDimitry Andric "Press Esc to cancel");
2272*5f7ddb14SDimitry Andric
2273*5f7ddb14SDimitry Andric Rect content_bounds = window.GetFrame();
2274*5f7ddb14SDimitry Andric content_bounds.Inset(2, 2);
2275*5f7ddb14SDimitry Andric DerivedWindow content_surface = DerivedWindow(window, content_bounds);
2276*5f7ddb14SDimitry Andric
2277*5f7ddb14SDimitry Andric DrawContent(content_surface);
2278*5f7ddb14SDimitry Andric return true;
2279*5f7ddb14SDimitry Andric }
2280*5f7ddb14SDimitry Andric
SkipNextHiddenFields()2281*5f7ddb14SDimitry Andric void SkipNextHiddenFields() {
2282*5f7ddb14SDimitry Andric while (true) {
2283*5f7ddb14SDimitry Andric if (m_delegate_sp->GetField(m_selection_index)->FieldDelegateIsVisible())
2284*5f7ddb14SDimitry Andric return;
2285*5f7ddb14SDimitry Andric
2286*5f7ddb14SDimitry Andric if (m_selection_index == m_delegate_sp->GetNumberOfFields() - 1) {
2287*5f7ddb14SDimitry Andric m_selection_type = SelectionType::Action;
2288*5f7ddb14SDimitry Andric m_selection_index = 0;
2289*5f7ddb14SDimitry Andric return;
2290*5f7ddb14SDimitry Andric }
2291*5f7ddb14SDimitry Andric
2292*5f7ddb14SDimitry Andric m_selection_index++;
2293*5f7ddb14SDimitry Andric }
2294*5f7ddb14SDimitry Andric }
2295*5f7ddb14SDimitry Andric
SelectNext(int key)2296*5f7ddb14SDimitry Andric HandleCharResult SelectNext(int key) {
2297*5f7ddb14SDimitry Andric if (m_selection_type == SelectionType::Action) {
2298*5f7ddb14SDimitry Andric if (m_selection_index < m_delegate_sp->GetNumberOfActions() - 1) {
2299*5f7ddb14SDimitry Andric m_selection_index++;
2300*5f7ddb14SDimitry Andric return eKeyHandled;
2301*5f7ddb14SDimitry Andric }
2302*5f7ddb14SDimitry Andric
2303*5f7ddb14SDimitry Andric m_selection_index = 0;
2304*5f7ddb14SDimitry Andric m_selection_type = SelectionType::Field;
2305*5f7ddb14SDimitry Andric SkipNextHiddenFields();
2306*5f7ddb14SDimitry Andric if (m_selection_type == SelectionType::Field) {
2307*5f7ddb14SDimitry Andric FieldDelegate *next_field = m_delegate_sp->GetField(m_selection_index);
2308*5f7ddb14SDimitry Andric next_field->FieldDelegateSelectFirstElement();
2309*5f7ddb14SDimitry Andric }
2310*5f7ddb14SDimitry Andric return eKeyHandled;
2311*5f7ddb14SDimitry Andric }
2312*5f7ddb14SDimitry Andric
2313*5f7ddb14SDimitry Andric FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);
2314*5f7ddb14SDimitry Andric if (!field->FieldDelegateOnLastOrOnlyElement()) {
2315*5f7ddb14SDimitry Andric return field->FieldDelegateHandleChar(key);
2316*5f7ddb14SDimitry Andric }
2317*5f7ddb14SDimitry Andric
2318*5f7ddb14SDimitry Andric field->FieldDelegateExitCallback();
2319*5f7ddb14SDimitry Andric
2320*5f7ddb14SDimitry Andric if (m_selection_index == m_delegate_sp->GetNumberOfFields() - 1) {
2321*5f7ddb14SDimitry Andric m_selection_type = SelectionType::Action;
2322*5f7ddb14SDimitry Andric m_selection_index = 0;
2323*5f7ddb14SDimitry Andric return eKeyHandled;
2324*5f7ddb14SDimitry Andric }
2325*5f7ddb14SDimitry Andric
2326*5f7ddb14SDimitry Andric m_selection_index++;
2327*5f7ddb14SDimitry Andric SkipNextHiddenFields();
2328*5f7ddb14SDimitry Andric
2329*5f7ddb14SDimitry Andric if (m_selection_type == SelectionType::Field) {
2330*5f7ddb14SDimitry Andric FieldDelegate *next_field = m_delegate_sp->GetField(m_selection_index);
2331*5f7ddb14SDimitry Andric next_field->FieldDelegateSelectFirstElement();
2332*5f7ddb14SDimitry Andric }
2333*5f7ddb14SDimitry Andric
2334*5f7ddb14SDimitry Andric return eKeyHandled;
2335*5f7ddb14SDimitry Andric }
2336*5f7ddb14SDimitry Andric
SkipPreviousHiddenFields()2337*5f7ddb14SDimitry Andric void SkipPreviousHiddenFields() {
2338*5f7ddb14SDimitry Andric while (true) {
2339*5f7ddb14SDimitry Andric if (m_delegate_sp->GetField(m_selection_index)->FieldDelegateIsVisible())
2340*5f7ddb14SDimitry Andric return;
2341*5f7ddb14SDimitry Andric
2342*5f7ddb14SDimitry Andric if (m_selection_index == 0) {
2343*5f7ddb14SDimitry Andric m_selection_type = SelectionType::Action;
2344*5f7ddb14SDimitry Andric m_selection_index = 0;
2345*5f7ddb14SDimitry Andric return;
2346*5f7ddb14SDimitry Andric }
2347*5f7ddb14SDimitry Andric
2348*5f7ddb14SDimitry Andric m_selection_index--;
2349*5f7ddb14SDimitry Andric }
2350*5f7ddb14SDimitry Andric }
2351*5f7ddb14SDimitry Andric
SelectPrevious(int key)2352*5f7ddb14SDimitry Andric HandleCharResult SelectPrevious(int key) {
2353*5f7ddb14SDimitry Andric if (m_selection_type == SelectionType::Action) {
2354*5f7ddb14SDimitry Andric if (m_selection_index > 0) {
2355*5f7ddb14SDimitry Andric m_selection_index--;
2356*5f7ddb14SDimitry Andric return eKeyHandled;
2357*5f7ddb14SDimitry Andric }
2358*5f7ddb14SDimitry Andric m_selection_index = m_delegate_sp->GetNumberOfFields() - 1;
2359*5f7ddb14SDimitry Andric m_selection_type = SelectionType::Field;
2360*5f7ddb14SDimitry Andric SkipPreviousHiddenFields();
2361*5f7ddb14SDimitry Andric if (m_selection_type == SelectionType::Field) {
2362*5f7ddb14SDimitry Andric FieldDelegate *previous_field =
2363*5f7ddb14SDimitry Andric m_delegate_sp->GetField(m_selection_index);
2364*5f7ddb14SDimitry Andric previous_field->FieldDelegateSelectLastElement();
2365*5f7ddb14SDimitry Andric }
2366*5f7ddb14SDimitry Andric return eKeyHandled;
2367*5f7ddb14SDimitry Andric }
2368*5f7ddb14SDimitry Andric
2369*5f7ddb14SDimitry Andric FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);
2370*5f7ddb14SDimitry Andric if (!field->FieldDelegateOnFirstOrOnlyElement()) {
2371*5f7ddb14SDimitry Andric return field->FieldDelegateHandleChar(key);
2372*5f7ddb14SDimitry Andric }
2373*5f7ddb14SDimitry Andric
2374*5f7ddb14SDimitry Andric field->FieldDelegateExitCallback();
2375*5f7ddb14SDimitry Andric
2376*5f7ddb14SDimitry Andric if (m_selection_index == 0) {
2377*5f7ddb14SDimitry Andric m_selection_type = SelectionType::Action;
2378*5f7ddb14SDimitry Andric m_selection_index = m_delegate_sp->GetNumberOfActions() - 1;
2379*5f7ddb14SDimitry Andric return eKeyHandled;
2380*5f7ddb14SDimitry Andric }
2381*5f7ddb14SDimitry Andric
2382*5f7ddb14SDimitry Andric m_selection_index--;
2383*5f7ddb14SDimitry Andric SkipPreviousHiddenFields();
2384*5f7ddb14SDimitry Andric
2385*5f7ddb14SDimitry Andric if (m_selection_type == SelectionType::Field) {
2386*5f7ddb14SDimitry Andric FieldDelegate *previous_field =
2387*5f7ddb14SDimitry Andric m_delegate_sp->GetField(m_selection_index);
2388*5f7ddb14SDimitry Andric previous_field->FieldDelegateSelectLastElement();
2389*5f7ddb14SDimitry Andric }
2390*5f7ddb14SDimitry Andric
2391*5f7ddb14SDimitry Andric return eKeyHandled;
2392*5f7ddb14SDimitry Andric }
2393*5f7ddb14SDimitry Andric
ExecuteAction(Window & window)2394*5f7ddb14SDimitry Andric void ExecuteAction(Window &window) {
2395*5f7ddb14SDimitry Andric FormAction &action = m_delegate_sp->GetAction(m_selection_index);
2396*5f7ddb14SDimitry Andric action.Execute(window);
2397*5f7ddb14SDimitry Andric if (m_delegate_sp->HasError()) {
2398*5f7ddb14SDimitry Andric m_first_visible_line = 0;
2399*5f7ddb14SDimitry Andric m_selection_index = 0;
2400*5f7ddb14SDimitry Andric m_selection_type = SelectionType::Field;
2401*5f7ddb14SDimitry Andric }
2402*5f7ddb14SDimitry Andric }
2403*5f7ddb14SDimitry Andric
WindowDelegateHandleChar(Window & window,int key)2404*5f7ddb14SDimitry Andric HandleCharResult WindowDelegateHandleChar(Window &window, int key) override {
2405*5f7ddb14SDimitry Andric switch (key) {
2406*5f7ddb14SDimitry Andric case '\r':
2407*5f7ddb14SDimitry Andric case '\n':
2408*5f7ddb14SDimitry Andric case KEY_ENTER:
2409*5f7ddb14SDimitry Andric if (m_selection_type == SelectionType::Action) {
2410*5f7ddb14SDimitry Andric ExecuteAction(window);
2411*5f7ddb14SDimitry Andric return eKeyHandled;
2412*5f7ddb14SDimitry Andric }
2413*5f7ddb14SDimitry Andric break;
2414*5f7ddb14SDimitry Andric case '\t':
2415*5f7ddb14SDimitry Andric return SelectNext(key);
2416*5f7ddb14SDimitry Andric case KEY_SHIFT_TAB:
2417*5f7ddb14SDimitry Andric return SelectPrevious(key);
2418*5f7ddb14SDimitry Andric case KEY_ESCAPE:
2419*5f7ddb14SDimitry Andric window.GetParent()->RemoveSubWindow(&window);
2420*5f7ddb14SDimitry Andric return eKeyHandled;
2421*5f7ddb14SDimitry Andric default:
2422*5f7ddb14SDimitry Andric break;
2423*5f7ddb14SDimitry Andric }
2424*5f7ddb14SDimitry Andric
2425*5f7ddb14SDimitry Andric // If the key wasn't handled and one of the fields is selected, pass the key
2426*5f7ddb14SDimitry Andric // to that field.
2427*5f7ddb14SDimitry Andric if (m_selection_type == SelectionType::Field) {
2428*5f7ddb14SDimitry Andric FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);
2429*5f7ddb14SDimitry Andric return field->FieldDelegateHandleChar(key);
2430*5f7ddb14SDimitry Andric }
2431*5f7ddb14SDimitry Andric
2432*5f7ddb14SDimitry Andric return eKeyNotHandled;
2433*5f7ddb14SDimitry Andric }
2434*5f7ddb14SDimitry Andric
2435*5f7ddb14SDimitry Andric protected:
2436*5f7ddb14SDimitry Andric FormDelegateSP m_delegate_sp;
2437*5f7ddb14SDimitry Andric // The index of the currently selected SelectionType.
2438*5f7ddb14SDimitry Andric int m_selection_index;
2439*5f7ddb14SDimitry Andric // See SelectionType class enum.
2440*5f7ddb14SDimitry Andric SelectionType m_selection_type;
2441*5f7ddb14SDimitry Andric // The first visible line from the pad.
2442*5f7ddb14SDimitry Andric int m_first_visible_line;
2443*5f7ddb14SDimitry Andric };
2444*5f7ddb14SDimitry Andric
2445*5f7ddb14SDimitry Andric ///////////////////////////
2446*5f7ddb14SDimitry Andric // Form Delegate Instances
2447*5f7ddb14SDimitry Andric ///////////////////////////
2448*5f7ddb14SDimitry Andric
2449*5f7ddb14SDimitry Andric class DetachOrKillProcessFormDelegate : public FormDelegate {
2450*5f7ddb14SDimitry Andric public:
DetachOrKillProcessFormDelegate(Process * process)2451*5f7ddb14SDimitry Andric DetachOrKillProcessFormDelegate(Process *process) : m_process(process) {
2452*5f7ddb14SDimitry Andric SetError("There is a running process, either detach or kill it.");
2453*5f7ddb14SDimitry Andric
2454*5f7ddb14SDimitry Andric m_keep_stopped_field =
2455*5f7ddb14SDimitry Andric AddBooleanField("Keep process stopped when detaching.", false);
2456*5f7ddb14SDimitry Andric
2457*5f7ddb14SDimitry Andric AddAction("Detach", [this](Window &window) { Detach(window); });
2458*5f7ddb14SDimitry Andric AddAction("Kill", [this](Window &window) { Kill(window); });
2459*5f7ddb14SDimitry Andric }
2460*5f7ddb14SDimitry Andric
GetName()2461*5f7ddb14SDimitry Andric std::string GetName() override { return "Detach/Kill Process"; }
2462*5f7ddb14SDimitry Andric
Kill(Window & window)2463*5f7ddb14SDimitry Andric void Kill(Window &window) {
2464*5f7ddb14SDimitry Andric Status destroy_status(m_process->Destroy(false));
2465*5f7ddb14SDimitry Andric if (destroy_status.Fail()) {
2466*5f7ddb14SDimitry Andric SetError("Failed to kill process.");
2467*5f7ddb14SDimitry Andric return;
2468*5f7ddb14SDimitry Andric }
2469*5f7ddb14SDimitry Andric window.GetParent()->RemoveSubWindow(&window);
2470*5f7ddb14SDimitry Andric }
2471*5f7ddb14SDimitry Andric
Detach(Window & window)2472*5f7ddb14SDimitry Andric void Detach(Window &window) {
2473*5f7ddb14SDimitry Andric Status detach_status(m_process->Detach(m_keep_stopped_field->GetBoolean()));
2474*5f7ddb14SDimitry Andric if (detach_status.Fail()) {
2475*5f7ddb14SDimitry Andric SetError("Failed to detach from process.");
2476*5f7ddb14SDimitry Andric return;
2477*5f7ddb14SDimitry Andric }
2478*5f7ddb14SDimitry Andric window.GetParent()->RemoveSubWindow(&window);
2479*5f7ddb14SDimitry Andric }
2480*5f7ddb14SDimitry Andric
2481*5f7ddb14SDimitry Andric protected:
2482*5f7ddb14SDimitry Andric Process *m_process;
2483*5f7ddb14SDimitry Andric BooleanFieldDelegate *m_keep_stopped_field;
2484*5f7ddb14SDimitry Andric };
2485*5f7ddb14SDimitry Andric
2486*5f7ddb14SDimitry Andric class ProcessAttachFormDelegate : public FormDelegate {
2487*5f7ddb14SDimitry Andric public:
ProcessAttachFormDelegate(Debugger & debugger,WindowSP main_window_sp)2488*5f7ddb14SDimitry Andric ProcessAttachFormDelegate(Debugger &debugger, WindowSP main_window_sp)
2489*5f7ddb14SDimitry Andric : m_debugger(debugger), m_main_window_sp(main_window_sp) {
2490*5f7ddb14SDimitry Andric std::vector<std::string> types;
2491*5f7ddb14SDimitry Andric types.push_back(std::string("Name"));
2492*5f7ddb14SDimitry Andric types.push_back(std::string("PID"));
2493*5f7ddb14SDimitry Andric m_type_field = AddChoicesField("Attach By", 2, types);
2494*5f7ddb14SDimitry Andric m_pid_field = AddIntegerField("PID", 0, true);
2495*5f7ddb14SDimitry Andric m_name_field =
2496*5f7ddb14SDimitry Andric AddTextField("Process Name", GetDefaultProcessName().c_str(), true);
2497*5f7ddb14SDimitry Andric m_continue_field = AddBooleanField("Continue once attached.", false);
2498*5f7ddb14SDimitry Andric m_wait_for_field = AddBooleanField("Wait for process to launch.", false);
2499*5f7ddb14SDimitry Andric m_include_existing_field =
2500*5f7ddb14SDimitry Andric AddBooleanField("Include existing processes.", false);
2501*5f7ddb14SDimitry Andric m_show_advanced_field = AddBooleanField("Show advanced settings.", false);
2502*5f7ddb14SDimitry Andric m_plugin_field = AddProcessPluginField();
2503*5f7ddb14SDimitry Andric
2504*5f7ddb14SDimitry Andric AddAction("Attach", [this](Window &window) { Attach(window); });
2505*5f7ddb14SDimitry Andric }
2506*5f7ddb14SDimitry Andric
GetName()2507*5f7ddb14SDimitry Andric std::string GetName() override { return "Attach Process"; }
2508*5f7ddb14SDimitry Andric
UpdateFieldsVisibility()2509*5f7ddb14SDimitry Andric void UpdateFieldsVisibility() override {
2510*5f7ddb14SDimitry Andric if (m_type_field->GetChoiceContent() == "Name") {
2511*5f7ddb14SDimitry Andric m_pid_field->FieldDelegateHide();
2512*5f7ddb14SDimitry Andric m_name_field->FieldDelegateShow();
2513*5f7ddb14SDimitry Andric m_wait_for_field->FieldDelegateShow();
2514*5f7ddb14SDimitry Andric if (m_wait_for_field->GetBoolean())
2515*5f7ddb14SDimitry Andric m_include_existing_field->FieldDelegateShow();
2516*5f7ddb14SDimitry Andric else
2517*5f7ddb14SDimitry Andric m_include_existing_field->FieldDelegateHide();
2518*5f7ddb14SDimitry Andric } else {
2519*5f7ddb14SDimitry Andric m_pid_field->FieldDelegateShow();
2520*5f7ddb14SDimitry Andric m_name_field->FieldDelegateHide();
2521*5f7ddb14SDimitry Andric m_wait_for_field->FieldDelegateHide();
2522*5f7ddb14SDimitry Andric m_include_existing_field->FieldDelegateHide();
2523*5f7ddb14SDimitry Andric }
2524*5f7ddb14SDimitry Andric if (m_show_advanced_field->GetBoolean())
2525*5f7ddb14SDimitry Andric m_plugin_field->FieldDelegateShow();
2526*5f7ddb14SDimitry Andric else
2527*5f7ddb14SDimitry Andric m_plugin_field->FieldDelegateHide();
2528*5f7ddb14SDimitry Andric }
2529*5f7ddb14SDimitry Andric
2530*5f7ddb14SDimitry Andric // Get the basename of the target's main executable if available, empty string
2531*5f7ddb14SDimitry Andric // otherwise.
GetDefaultProcessName()2532*5f7ddb14SDimitry Andric std::string GetDefaultProcessName() {
2533*5f7ddb14SDimitry Andric Target *target = m_debugger.GetSelectedTarget().get();
2534*5f7ddb14SDimitry Andric if (target == nullptr)
2535*5f7ddb14SDimitry Andric return "";
2536*5f7ddb14SDimitry Andric
2537*5f7ddb14SDimitry Andric ModuleSP module_sp = target->GetExecutableModule();
2538*5f7ddb14SDimitry Andric if (!module_sp->IsExecutable())
2539*5f7ddb14SDimitry Andric return "";
2540*5f7ddb14SDimitry Andric
2541*5f7ddb14SDimitry Andric return module_sp->GetFileSpec().GetFilename().AsCString();
2542*5f7ddb14SDimitry Andric }
2543*5f7ddb14SDimitry Andric
StopRunningProcess()2544*5f7ddb14SDimitry Andric bool StopRunningProcess() {
2545*5f7ddb14SDimitry Andric ExecutionContext exe_ctx =
2546*5f7ddb14SDimitry Andric m_debugger.GetCommandInterpreter().GetExecutionContext();
2547*5f7ddb14SDimitry Andric
2548*5f7ddb14SDimitry Andric if (!exe_ctx.HasProcessScope())
2549*5f7ddb14SDimitry Andric return false;
2550*5f7ddb14SDimitry Andric
2551*5f7ddb14SDimitry Andric Process *process = exe_ctx.GetProcessPtr();
2552*5f7ddb14SDimitry Andric if (!(process && process->IsAlive()))
2553*5f7ddb14SDimitry Andric return false;
2554*5f7ddb14SDimitry Andric
2555*5f7ddb14SDimitry Andric FormDelegateSP form_delegate_sp =
2556*5f7ddb14SDimitry Andric FormDelegateSP(new DetachOrKillProcessFormDelegate(process));
2557*5f7ddb14SDimitry Andric Rect bounds = m_main_window_sp->GetCenteredRect(85, 8);
2558*5f7ddb14SDimitry Andric WindowSP form_window_sp = m_main_window_sp->CreateSubWindow(
2559*5f7ddb14SDimitry Andric form_delegate_sp->GetName().c_str(), bounds, true);
2560*5f7ddb14SDimitry Andric WindowDelegateSP window_delegate_sp =
2561*5f7ddb14SDimitry Andric WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));
2562*5f7ddb14SDimitry Andric form_window_sp->SetDelegate(window_delegate_sp);
2563*5f7ddb14SDimitry Andric
2564*5f7ddb14SDimitry Andric return true;
2565*5f7ddb14SDimitry Andric }
2566*5f7ddb14SDimitry Andric
GetTarget()2567*5f7ddb14SDimitry Andric Target *GetTarget() {
2568*5f7ddb14SDimitry Andric Target *target = m_debugger.GetSelectedTarget().get();
2569*5f7ddb14SDimitry Andric
2570*5f7ddb14SDimitry Andric if (target != nullptr)
2571*5f7ddb14SDimitry Andric return target;
2572*5f7ddb14SDimitry Andric
2573*5f7ddb14SDimitry Andric TargetSP new_target_sp;
2574*5f7ddb14SDimitry Andric m_debugger.GetTargetList().CreateTarget(
2575*5f7ddb14SDimitry Andric m_debugger, "", "", eLoadDependentsNo, nullptr, new_target_sp);
2576*5f7ddb14SDimitry Andric
2577*5f7ddb14SDimitry Andric target = new_target_sp.get();
2578*5f7ddb14SDimitry Andric
2579*5f7ddb14SDimitry Andric if (target == nullptr)
2580*5f7ddb14SDimitry Andric SetError("Failed to create target.");
2581*5f7ddb14SDimitry Andric
2582*5f7ddb14SDimitry Andric m_debugger.GetTargetList().SetSelectedTarget(new_target_sp);
2583*5f7ddb14SDimitry Andric
2584*5f7ddb14SDimitry Andric return target;
2585*5f7ddb14SDimitry Andric }
2586*5f7ddb14SDimitry Andric
GetAttachInfo()2587*5f7ddb14SDimitry Andric ProcessAttachInfo GetAttachInfo() {
2588*5f7ddb14SDimitry Andric ProcessAttachInfo attach_info;
2589*5f7ddb14SDimitry Andric attach_info.SetContinueOnceAttached(m_continue_field->GetBoolean());
2590*5f7ddb14SDimitry Andric if (m_type_field->GetChoiceContent() == "Name") {
2591*5f7ddb14SDimitry Andric attach_info.GetExecutableFile().SetFile(m_name_field->GetText(),
2592*5f7ddb14SDimitry Andric FileSpec::Style::native);
2593*5f7ddb14SDimitry Andric attach_info.SetWaitForLaunch(m_wait_for_field->GetBoolean());
2594*5f7ddb14SDimitry Andric if (m_wait_for_field->GetBoolean())
2595*5f7ddb14SDimitry Andric attach_info.SetIgnoreExisting(!m_include_existing_field->GetBoolean());
2596*5f7ddb14SDimitry Andric } else {
2597*5f7ddb14SDimitry Andric attach_info.SetProcessID(m_pid_field->GetInteger());
2598*5f7ddb14SDimitry Andric }
2599*5f7ddb14SDimitry Andric attach_info.SetProcessPluginName(m_plugin_field->GetPluginName());
2600*5f7ddb14SDimitry Andric
2601*5f7ddb14SDimitry Andric return attach_info;
2602*5f7ddb14SDimitry Andric }
2603*5f7ddb14SDimitry Andric
Attach(Window & window)2604*5f7ddb14SDimitry Andric void Attach(Window &window) {
2605*5f7ddb14SDimitry Andric ClearError();
2606*5f7ddb14SDimitry Andric
2607*5f7ddb14SDimitry Andric bool all_fields_are_valid = CheckFieldsValidity();
2608*5f7ddb14SDimitry Andric if (!all_fields_are_valid)
2609*5f7ddb14SDimitry Andric return;
2610*5f7ddb14SDimitry Andric
2611*5f7ddb14SDimitry Andric bool process_is_running = StopRunningProcess();
2612*5f7ddb14SDimitry Andric if (process_is_running)
2613*5f7ddb14SDimitry Andric return;
2614*5f7ddb14SDimitry Andric
2615*5f7ddb14SDimitry Andric Target *target = GetTarget();
2616*5f7ddb14SDimitry Andric if (HasError())
2617*5f7ddb14SDimitry Andric return;
2618*5f7ddb14SDimitry Andric
2619*5f7ddb14SDimitry Andric StreamString stream;
2620*5f7ddb14SDimitry Andric ProcessAttachInfo attach_info = GetAttachInfo();
2621*5f7ddb14SDimitry Andric Status status = target->Attach(attach_info, &stream);
2622*5f7ddb14SDimitry Andric
2623*5f7ddb14SDimitry Andric if (status.Fail()) {
2624*5f7ddb14SDimitry Andric SetError(status.AsCString());
2625*5f7ddb14SDimitry Andric return;
2626*5f7ddb14SDimitry Andric }
2627*5f7ddb14SDimitry Andric
2628*5f7ddb14SDimitry Andric ProcessSP process_sp(target->GetProcessSP());
2629*5f7ddb14SDimitry Andric if (!process_sp) {
2630*5f7ddb14SDimitry Andric SetError("Attached sucessfully but target has no process.");
2631*5f7ddb14SDimitry Andric return;
2632*5f7ddb14SDimitry Andric }
2633*5f7ddb14SDimitry Andric
2634*5f7ddb14SDimitry Andric if (attach_info.GetContinueOnceAttached())
2635*5f7ddb14SDimitry Andric process_sp->Resume();
2636*5f7ddb14SDimitry Andric
2637*5f7ddb14SDimitry Andric window.GetParent()->RemoveSubWindow(&window);
2638*5f7ddb14SDimitry Andric }
2639*5f7ddb14SDimitry Andric
2640*5f7ddb14SDimitry Andric protected:
2641*5f7ddb14SDimitry Andric Debugger &m_debugger;
2642*5f7ddb14SDimitry Andric WindowSP m_main_window_sp;
2643*5f7ddb14SDimitry Andric
2644*5f7ddb14SDimitry Andric ChoicesFieldDelegate *m_type_field;
2645*5f7ddb14SDimitry Andric IntegerFieldDelegate *m_pid_field;
2646*5f7ddb14SDimitry Andric TextFieldDelegate *m_name_field;
2647*5f7ddb14SDimitry Andric BooleanFieldDelegate *m_continue_field;
2648*5f7ddb14SDimitry Andric BooleanFieldDelegate *m_wait_for_field;
2649*5f7ddb14SDimitry Andric BooleanFieldDelegate *m_include_existing_field;
2650*5f7ddb14SDimitry Andric BooleanFieldDelegate *m_show_advanced_field;
2651*5f7ddb14SDimitry Andric ProcessPluginFieldDelegate *m_plugin_field;
2652*5f7ddb14SDimitry Andric };
2653*5f7ddb14SDimitry Andric
2654480093f4SDimitry Andric class MenuDelegate {
2655480093f4SDimitry Andric public:
2656480093f4SDimitry Andric virtual ~MenuDelegate() = default;
2657480093f4SDimitry Andric
2658480093f4SDimitry Andric virtual MenuActionResult MenuDelegateAction(Menu &menu) = 0;
2659480093f4SDimitry Andric };
2660480093f4SDimitry Andric
2661480093f4SDimitry Andric class Menu : public WindowDelegate {
2662480093f4SDimitry Andric public:
2663480093f4SDimitry Andric enum class Type { Invalid, Bar, Item, Separator };
2664480093f4SDimitry Andric
2665480093f4SDimitry Andric // Menubar or separator constructor
2666480093f4SDimitry Andric Menu(Type type);
2667480093f4SDimitry Andric
2668480093f4SDimitry Andric // Menuitem constructor
2669480093f4SDimitry Andric Menu(const char *name, const char *key_name, int key_value,
2670480093f4SDimitry Andric uint64_t identifier);
2671480093f4SDimitry Andric
2672480093f4SDimitry Andric ~Menu() override = default;
2673480093f4SDimitry Andric
GetDelegate() const2674480093f4SDimitry Andric const MenuDelegateSP &GetDelegate() const { return m_delegate_sp; }
2675480093f4SDimitry Andric
SetDelegate(const MenuDelegateSP & delegate_sp)2676480093f4SDimitry Andric void SetDelegate(const MenuDelegateSP &delegate_sp) {
2677480093f4SDimitry Andric m_delegate_sp = delegate_sp;
2678480093f4SDimitry Andric }
2679480093f4SDimitry Andric
2680480093f4SDimitry Andric void RecalculateNameLengths();
2681480093f4SDimitry Andric
2682480093f4SDimitry Andric void AddSubmenu(const MenuSP &menu_sp);
2683480093f4SDimitry Andric
2684480093f4SDimitry Andric int DrawAndRunMenu(Window &window);
2685480093f4SDimitry Andric
2686480093f4SDimitry Andric void DrawMenuTitle(Window &window, bool highlight);
2687480093f4SDimitry Andric
2688480093f4SDimitry Andric bool WindowDelegateDraw(Window &window, bool force) override;
2689480093f4SDimitry Andric
2690480093f4SDimitry Andric HandleCharResult WindowDelegateHandleChar(Window &window, int key) override;
2691480093f4SDimitry Andric
ActionPrivate(Menu & menu)2692480093f4SDimitry Andric MenuActionResult ActionPrivate(Menu &menu) {
2693480093f4SDimitry Andric MenuActionResult result = MenuActionResult::NotHandled;
2694480093f4SDimitry Andric if (m_delegate_sp) {
2695480093f4SDimitry Andric result = m_delegate_sp->MenuDelegateAction(menu);
2696480093f4SDimitry Andric if (result != MenuActionResult::NotHandled)
2697480093f4SDimitry Andric return result;
2698480093f4SDimitry Andric } else if (m_parent) {
2699480093f4SDimitry Andric result = m_parent->ActionPrivate(menu);
2700480093f4SDimitry Andric if (result != MenuActionResult::NotHandled)
2701480093f4SDimitry Andric return result;
2702480093f4SDimitry Andric }
2703480093f4SDimitry Andric return m_canned_result;
2704480093f4SDimitry Andric }
2705480093f4SDimitry Andric
Action()2706480093f4SDimitry Andric MenuActionResult Action() {
2707480093f4SDimitry Andric // Call the recursive action so it can try to handle it with the menu
2708480093f4SDimitry Andric // delegate, and if not, try our parent menu
2709480093f4SDimitry Andric return ActionPrivate(*this);
2710480093f4SDimitry Andric }
2711480093f4SDimitry Andric
SetCannedResult(MenuActionResult result)2712480093f4SDimitry Andric void SetCannedResult(MenuActionResult result) { m_canned_result = result; }
2713480093f4SDimitry Andric
GetSubmenus()2714480093f4SDimitry Andric Menus &GetSubmenus() { return m_submenus; }
2715480093f4SDimitry Andric
GetSubmenus() const2716480093f4SDimitry Andric const Menus &GetSubmenus() const { return m_submenus; }
2717480093f4SDimitry Andric
GetSelectedSubmenuIndex() const2718480093f4SDimitry Andric int GetSelectedSubmenuIndex() const { return m_selected; }
2719480093f4SDimitry Andric
SetSelectedSubmenuIndex(int idx)2720480093f4SDimitry Andric void SetSelectedSubmenuIndex(int idx) { m_selected = idx; }
2721480093f4SDimitry Andric
GetType() const2722480093f4SDimitry Andric Type GetType() const { return m_type; }
2723480093f4SDimitry Andric
GetStartingColumn() const2724480093f4SDimitry Andric int GetStartingColumn() const { return m_start_col; }
2725480093f4SDimitry Andric
SetStartingColumn(int col)2726480093f4SDimitry Andric void SetStartingColumn(int col) { m_start_col = col; }
2727480093f4SDimitry Andric
GetKeyValue() const2728480093f4SDimitry Andric int GetKeyValue() const { return m_key_value; }
2729480093f4SDimitry Andric
GetName()2730480093f4SDimitry Andric std::string &GetName() { return m_name; }
2731480093f4SDimitry Andric
GetDrawWidth() const2732480093f4SDimitry Andric int GetDrawWidth() const {
2733480093f4SDimitry Andric return m_max_submenu_name_length + m_max_submenu_key_name_length + 8;
2734480093f4SDimitry Andric }
2735480093f4SDimitry Andric
GetIdentifier() const2736480093f4SDimitry Andric uint64_t GetIdentifier() const { return m_identifier; }
2737480093f4SDimitry Andric
SetIdentifier(uint64_t identifier)2738480093f4SDimitry Andric void SetIdentifier(uint64_t identifier) { m_identifier = identifier; }
2739480093f4SDimitry Andric
2740480093f4SDimitry Andric protected:
2741480093f4SDimitry Andric std::string m_name;
2742480093f4SDimitry Andric std::string m_key_name;
2743480093f4SDimitry Andric uint64_t m_identifier;
2744480093f4SDimitry Andric Type m_type;
2745480093f4SDimitry Andric int m_key_value;
2746480093f4SDimitry Andric int m_start_col;
2747480093f4SDimitry Andric int m_max_submenu_name_length;
2748480093f4SDimitry Andric int m_max_submenu_key_name_length;
2749480093f4SDimitry Andric int m_selected;
2750480093f4SDimitry Andric Menu *m_parent;
2751480093f4SDimitry Andric Menus m_submenus;
2752480093f4SDimitry Andric WindowSP m_menu_window_sp;
2753480093f4SDimitry Andric MenuActionResult m_canned_result;
2754480093f4SDimitry Andric MenuDelegateSP m_delegate_sp;
2755480093f4SDimitry Andric };
2756480093f4SDimitry Andric
2757480093f4SDimitry Andric // Menubar or separator constructor
Menu(Type type)2758480093f4SDimitry Andric Menu::Menu(Type type)
2759480093f4SDimitry Andric : m_name(), m_key_name(), m_identifier(0), m_type(type), m_key_value(0),
2760480093f4SDimitry Andric m_start_col(0), m_max_submenu_name_length(0),
2761480093f4SDimitry Andric m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr),
2762480093f4SDimitry Andric m_submenus(), m_canned_result(MenuActionResult::NotHandled),
2763480093f4SDimitry Andric m_delegate_sp() {}
2764480093f4SDimitry Andric
2765480093f4SDimitry Andric // Menuitem constructor
Menu(const char * name,const char * key_name,int key_value,uint64_t identifier)2766480093f4SDimitry Andric Menu::Menu(const char *name, const char *key_name, int key_value,
2767480093f4SDimitry Andric uint64_t identifier)
2768480093f4SDimitry Andric : m_name(), m_key_name(), m_identifier(identifier), m_type(Type::Invalid),
2769480093f4SDimitry Andric m_key_value(key_value), m_start_col(0), m_max_submenu_name_length(0),
2770480093f4SDimitry Andric m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr),
2771480093f4SDimitry Andric m_submenus(), m_canned_result(MenuActionResult::NotHandled),
2772480093f4SDimitry Andric m_delegate_sp() {
2773480093f4SDimitry Andric if (name && name[0]) {
2774480093f4SDimitry Andric m_name = name;
2775480093f4SDimitry Andric m_type = Type::Item;
2776480093f4SDimitry Andric if (key_name && key_name[0])
2777480093f4SDimitry Andric m_key_name = key_name;
2778480093f4SDimitry Andric } else {
2779480093f4SDimitry Andric m_type = Type::Separator;
2780480093f4SDimitry Andric }
2781480093f4SDimitry Andric }
2782480093f4SDimitry Andric
RecalculateNameLengths()2783480093f4SDimitry Andric void Menu::RecalculateNameLengths() {
2784480093f4SDimitry Andric m_max_submenu_name_length = 0;
2785480093f4SDimitry Andric m_max_submenu_key_name_length = 0;
2786480093f4SDimitry Andric Menus &submenus = GetSubmenus();
2787480093f4SDimitry Andric const size_t num_submenus = submenus.size();
2788480093f4SDimitry Andric for (size_t i = 0; i < num_submenus; ++i) {
2789480093f4SDimitry Andric Menu *submenu = submenus[i].get();
2790480093f4SDimitry Andric if (static_cast<size_t>(m_max_submenu_name_length) < submenu->m_name.size())
2791480093f4SDimitry Andric m_max_submenu_name_length = submenu->m_name.size();
2792480093f4SDimitry Andric if (static_cast<size_t>(m_max_submenu_key_name_length) <
2793480093f4SDimitry Andric submenu->m_key_name.size())
2794480093f4SDimitry Andric m_max_submenu_key_name_length = submenu->m_key_name.size();
2795480093f4SDimitry Andric }
2796480093f4SDimitry Andric }
2797480093f4SDimitry Andric
AddSubmenu(const MenuSP & menu_sp)2798480093f4SDimitry Andric void Menu::AddSubmenu(const MenuSP &menu_sp) {
2799480093f4SDimitry Andric menu_sp->m_parent = this;
2800480093f4SDimitry Andric if (static_cast<size_t>(m_max_submenu_name_length) < menu_sp->m_name.size())
2801480093f4SDimitry Andric m_max_submenu_name_length = menu_sp->m_name.size();
2802480093f4SDimitry Andric if (static_cast<size_t>(m_max_submenu_key_name_length) <
2803480093f4SDimitry Andric menu_sp->m_key_name.size())
2804480093f4SDimitry Andric m_max_submenu_key_name_length = menu_sp->m_key_name.size();
2805480093f4SDimitry Andric m_submenus.push_back(menu_sp);
2806480093f4SDimitry Andric }
2807480093f4SDimitry Andric
DrawMenuTitle(Window & window,bool highlight)2808480093f4SDimitry Andric void Menu::DrawMenuTitle(Window &window, bool highlight) {
2809480093f4SDimitry Andric if (m_type == Type::Separator) {
2810480093f4SDimitry Andric window.MoveCursor(0, window.GetCursorY());
2811480093f4SDimitry Andric window.PutChar(ACS_LTEE);
2812480093f4SDimitry Andric int width = window.GetWidth();
2813480093f4SDimitry Andric if (width > 2) {
2814480093f4SDimitry Andric width -= 2;
2815480093f4SDimitry Andric for (int i = 0; i < width; ++i)
2816480093f4SDimitry Andric window.PutChar(ACS_HLINE);
2817480093f4SDimitry Andric }
2818480093f4SDimitry Andric window.PutChar(ACS_RTEE);
2819480093f4SDimitry Andric } else {
2820480093f4SDimitry Andric const int shortcut_key = m_key_value;
2821480093f4SDimitry Andric bool underlined_shortcut = false;
2822af732203SDimitry Andric const attr_t highlight_attr = A_REVERSE;
2823480093f4SDimitry Andric if (highlight)
2824af732203SDimitry Andric window.AttributeOn(highlight_attr);
28255ffd83dbSDimitry Andric if (llvm::isPrint(shortcut_key)) {
2826480093f4SDimitry Andric size_t lower_pos = m_name.find(tolower(shortcut_key));
2827480093f4SDimitry Andric size_t upper_pos = m_name.find(toupper(shortcut_key));
2828480093f4SDimitry Andric const char *name = m_name.c_str();
2829480093f4SDimitry Andric size_t pos = std::min<size_t>(lower_pos, upper_pos);
2830480093f4SDimitry Andric if (pos != std::string::npos) {
2831480093f4SDimitry Andric underlined_shortcut = true;
2832480093f4SDimitry Andric if (pos > 0) {
2833480093f4SDimitry Andric window.PutCString(name, pos);
2834480093f4SDimitry Andric name += pos;
2835480093f4SDimitry Andric }
2836480093f4SDimitry Andric const attr_t shortcut_attr = A_UNDERLINE | A_BOLD;
2837480093f4SDimitry Andric window.AttributeOn(shortcut_attr);
2838480093f4SDimitry Andric window.PutChar(name[0]);
2839480093f4SDimitry Andric window.AttributeOff(shortcut_attr);
2840480093f4SDimitry Andric name++;
2841480093f4SDimitry Andric if (name[0])
2842480093f4SDimitry Andric window.PutCString(name);
2843480093f4SDimitry Andric }
2844480093f4SDimitry Andric }
2845480093f4SDimitry Andric
2846480093f4SDimitry Andric if (!underlined_shortcut) {
2847480093f4SDimitry Andric window.PutCString(m_name.c_str());
2848480093f4SDimitry Andric }
2849480093f4SDimitry Andric
2850480093f4SDimitry Andric if (highlight)
2851af732203SDimitry Andric window.AttributeOff(highlight_attr);
2852480093f4SDimitry Andric
2853480093f4SDimitry Andric if (m_key_name.empty()) {
28545ffd83dbSDimitry Andric if (!underlined_shortcut && llvm::isPrint(m_key_value)) {
2855af732203SDimitry Andric window.AttributeOn(COLOR_PAIR(MagentaOnWhite));
2856480093f4SDimitry Andric window.Printf(" (%c)", m_key_value);
2857af732203SDimitry Andric window.AttributeOff(COLOR_PAIR(MagentaOnWhite));
2858480093f4SDimitry Andric }
2859480093f4SDimitry Andric } else {
2860af732203SDimitry Andric window.AttributeOn(COLOR_PAIR(MagentaOnWhite));
2861480093f4SDimitry Andric window.Printf(" (%s)", m_key_name.c_str());
2862af732203SDimitry Andric window.AttributeOff(COLOR_PAIR(MagentaOnWhite));
2863480093f4SDimitry Andric }
2864480093f4SDimitry Andric }
2865480093f4SDimitry Andric }
2866480093f4SDimitry Andric
WindowDelegateDraw(Window & window,bool force)2867480093f4SDimitry Andric bool Menu::WindowDelegateDraw(Window &window, bool force) {
2868480093f4SDimitry Andric Menus &submenus = GetSubmenus();
2869480093f4SDimitry Andric const size_t num_submenus = submenus.size();
2870480093f4SDimitry Andric const int selected_idx = GetSelectedSubmenuIndex();
2871480093f4SDimitry Andric Menu::Type menu_type = GetType();
2872480093f4SDimitry Andric switch (menu_type) {
2873480093f4SDimitry Andric case Menu::Type::Bar: {
2874af732203SDimitry Andric window.SetBackground(BlackOnWhite);
2875480093f4SDimitry Andric window.MoveCursor(0, 0);
2876480093f4SDimitry Andric for (size_t i = 0; i < num_submenus; ++i) {
2877480093f4SDimitry Andric Menu *menu = submenus[i].get();
2878480093f4SDimitry Andric if (i > 0)
2879480093f4SDimitry Andric window.PutChar(' ');
2880480093f4SDimitry Andric menu->SetStartingColumn(window.GetCursorX());
2881480093f4SDimitry Andric window.PutCString("| ");
2882480093f4SDimitry Andric menu->DrawMenuTitle(window, false);
2883480093f4SDimitry Andric }
2884480093f4SDimitry Andric window.PutCString(" |");
2885480093f4SDimitry Andric } break;
2886480093f4SDimitry Andric
2887480093f4SDimitry Andric case Menu::Type::Item: {
2888480093f4SDimitry Andric int y = 1;
2889480093f4SDimitry Andric int x = 3;
2890480093f4SDimitry Andric // Draw the menu
2891480093f4SDimitry Andric int cursor_x = 0;
2892480093f4SDimitry Andric int cursor_y = 0;
2893480093f4SDimitry Andric window.Erase();
2894af732203SDimitry Andric window.SetBackground(BlackOnWhite);
2895480093f4SDimitry Andric window.Box();
2896480093f4SDimitry Andric for (size_t i = 0; i < num_submenus; ++i) {
2897480093f4SDimitry Andric const bool is_selected = (i == static_cast<size_t>(selected_idx));
2898480093f4SDimitry Andric window.MoveCursor(x, y + i);
2899480093f4SDimitry Andric if (is_selected) {
2900480093f4SDimitry Andric // Remember where we want the cursor to be
2901480093f4SDimitry Andric cursor_x = x - 1;
2902480093f4SDimitry Andric cursor_y = y + i;
2903480093f4SDimitry Andric }
2904480093f4SDimitry Andric submenus[i]->DrawMenuTitle(window, is_selected);
2905480093f4SDimitry Andric }
2906480093f4SDimitry Andric window.MoveCursor(cursor_x, cursor_y);
2907480093f4SDimitry Andric } break;
2908480093f4SDimitry Andric
2909480093f4SDimitry Andric default:
2910480093f4SDimitry Andric case Menu::Type::Separator:
2911480093f4SDimitry Andric break;
2912480093f4SDimitry Andric }
2913480093f4SDimitry Andric return true; // Drawing handled...
2914480093f4SDimitry Andric }
2915480093f4SDimitry Andric
WindowDelegateHandleChar(Window & window,int key)2916480093f4SDimitry Andric HandleCharResult Menu::WindowDelegateHandleChar(Window &window, int key) {
2917480093f4SDimitry Andric HandleCharResult result = eKeyNotHandled;
2918480093f4SDimitry Andric
2919480093f4SDimitry Andric Menus &submenus = GetSubmenus();
2920480093f4SDimitry Andric const size_t num_submenus = submenus.size();
2921480093f4SDimitry Andric const int selected_idx = GetSelectedSubmenuIndex();
2922480093f4SDimitry Andric Menu::Type menu_type = GetType();
2923480093f4SDimitry Andric if (menu_type == Menu::Type::Bar) {
2924480093f4SDimitry Andric MenuSP run_menu_sp;
2925480093f4SDimitry Andric switch (key) {
2926480093f4SDimitry Andric case KEY_DOWN:
2927480093f4SDimitry Andric case KEY_UP:
2928480093f4SDimitry Andric // Show last menu or first menu
2929480093f4SDimitry Andric if (selected_idx < static_cast<int>(num_submenus))
2930480093f4SDimitry Andric run_menu_sp = submenus[selected_idx];
2931480093f4SDimitry Andric else if (!submenus.empty())
2932480093f4SDimitry Andric run_menu_sp = submenus.front();
2933480093f4SDimitry Andric result = eKeyHandled;
2934480093f4SDimitry Andric break;
2935480093f4SDimitry Andric
2936480093f4SDimitry Andric case KEY_RIGHT:
2937480093f4SDimitry Andric ++m_selected;
2938480093f4SDimitry Andric if (m_selected >= static_cast<int>(num_submenus))
2939480093f4SDimitry Andric m_selected = 0;
2940480093f4SDimitry Andric if (m_selected < static_cast<int>(num_submenus))
2941480093f4SDimitry Andric run_menu_sp = submenus[m_selected];
2942480093f4SDimitry Andric else if (!submenus.empty())
2943480093f4SDimitry Andric run_menu_sp = submenus.front();
2944480093f4SDimitry Andric result = eKeyHandled;
2945480093f4SDimitry Andric break;
2946480093f4SDimitry Andric
2947480093f4SDimitry Andric case KEY_LEFT:
2948480093f4SDimitry Andric --m_selected;
2949480093f4SDimitry Andric if (m_selected < 0)
2950480093f4SDimitry Andric m_selected = num_submenus - 1;
2951480093f4SDimitry Andric if (m_selected < static_cast<int>(num_submenus))
2952480093f4SDimitry Andric run_menu_sp = submenus[m_selected];
2953480093f4SDimitry Andric else if (!submenus.empty())
2954480093f4SDimitry Andric run_menu_sp = submenus.front();
2955480093f4SDimitry Andric result = eKeyHandled;
2956480093f4SDimitry Andric break;
2957480093f4SDimitry Andric
2958480093f4SDimitry Andric default:
2959480093f4SDimitry Andric for (size_t i = 0; i < num_submenus; ++i) {
2960480093f4SDimitry Andric if (submenus[i]->GetKeyValue() == key) {
2961480093f4SDimitry Andric SetSelectedSubmenuIndex(i);
2962480093f4SDimitry Andric run_menu_sp = submenus[i];
2963480093f4SDimitry Andric result = eKeyHandled;
2964480093f4SDimitry Andric break;
2965480093f4SDimitry Andric }
2966480093f4SDimitry Andric }
2967480093f4SDimitry Andric break;
2968480093f4SDimitry Andric }
2969480093f4SDimitry Andric
2970480093f4SDimitry Andric if (run_menu_sp) {
2971480093f4SDimitry Andric // Run the action on this menu in case we need to populate the menu with
2972480093f4SDimitry Andric // dynamic content and also in case check marks, and any other menu
2973480093f4SDimitry Andric // decorations need to be calculated
2974480093f4SDimitry Andric if (run_menu_sp->Action() == MenuActionResult::Quit)
2975480093f4SDimitry Andric return eQuitApplication;
2976480093f4SDimitry Andric
2977480093f4SDimitry Andric Rect menu_bounds;
2978480093f4SDimitry Andric menu_bounds.origin.x = run_menu_sp->GetStartingColumn();
2979480093f4SDimitry Andric menu_bounds.origin.y = 1;
2980480093f4SDimitry Andric menu_bounds.size.width = run_menu_sp->GetDrawWidth();
2981480093f4SDimitry Andric menu_bounds.size.height = run_menu_sp->GetSubmenus().size() + 2;
2982480093f4SDimitry Andric if (m_menu_window_sp)
2983480093f4SDimitry Andric window.GetParent()->RemoveSubWindow(m_menu_window_sp.get());
2984480093f4SDimitry Andric
2985480093f4SDimitry Andric m_menu_window_sp = window.GetParent()->CreateSubWindow(
2986480093f4SDimitry Andric run_menu_sp->GetName().c_str(), menu_bounds, true);
2987480093f4SDimitry Andric m_menu_window_sp->SetDelegate(run_menu_sp);
2988480093f4SDimitry Andric }
2989480093f4SDimitry Andric } else if (menu_type == Menu::Type::Item) {
2990480093f4SDimitry Andric switch (key) {
2991480093f4SDimitry Andric case KEY_DOWN:
2992480093f4SDimitry Andric if (m_submenus.size() > 1) {
2993480093f4SDimitry Andric const int start_select = m_selected;
2994480093f4SDimitry Andric while (++m_selected != start_select) {
2995480093f4SDimitry Andric if (static_cast<size_t>(m_selected) >= num_submenus)
2996480093f4SDimitry Andric m_selected = 0;
2997480093f4SDimitry Andric if (m_submenus[m_selected]->GetType() == Type::Separator)
2998480093f4SDimitry Andric continue;
2999480093f4SDimitry Andric else
3000480093f4SDimitry Andric break;
3001480093f4SDimitry Andric }
3002480093f4SDimitry Andric return eKeyHandled;
3003480093f4SDimitry Andric }
3004480093f4SDimitry Andric break;
3005480093f4SDimitry Andric
3006480093f4SDimitry Andric case KEY_UP:
3007480093f4SDimitry Andric if (m_submenus.size() > 1) {
3008480093f4SDimitry Andric const int start_select = m_selected;
3009480093f4SDimitry Andric while (--m_selected != start_select) {
3010480093f4SDimitry Andric if (m_selected < static_cast<int>(0))
3011480093f4SDimitry Andric m_selected = num_submenus - 1;
3012480093f4SDimitry Andric if (m_submenus[m_selected]->GetType() == Type::Separator)
3013480093f4SDimitry Andric continue;
3014480093f4SDimitry Andric else
3015480093f4SDimitry Andric break;
3016480093f4SDimitry Andric }
3017480093f4SDimitry Andric return eKeyHandled;
3018480093f4SDimitry Andric }
3019480093f4SDimitry Andric break;
3020480093f4SDimitry Andric
3021480093f4SDimitry Andric case KEY_RETURN:
3022480093f4SDimitry Andric if (static_cast<size_t>(selected_idx) < num_submenus) {
3023480093f4SDimitry Andric if (submenus[selected_idx]->Action() == MenuActionResult::Quit)
3024480093f4SDimitry Andric return eQuitApplication;
3025480093f4SDimitry Andric window.GetParent()->RemoveSubWindow(&window);
3026480093f4SDimitry Andric return eKeyHandled;
3027480093f4SDimitry Andric }
3028480093f4SDimitry Andric break;
3029480093f4SDimitry Andric
3030480093f4SDimitry Andric case KEY_ESCAPE: // Beware: pressing escape key has 1 to 2 second delay in
3031480093f4SDimitry Andric // case other chars are entered for escaped sequences
3032480093f4SDimitry Andric window.GetParent()->RemoveSubWindow(&window);
3033480093f4SDimitry Andric return eKeyHandled;
3034480093f4SDimitry Andric
3035480093f4SDimitry Andric default:
3036480093f4SDimitry Andric for (size_t i = 0; i < num_submenus; ++i) {
3037480093f4SDimitry Andric Menu *menu = submenus[i].get();
3038480093f4SDimitry Andric if (menu->GetKeyValue() == key) {
3039480093f4SDimitry Andric SetSelectedSubmenuIndex(i);
3040480093f4SDimitry Andric window.GetParent()->RemoveSubWindow(&window);
3041480093f4SDimitry Andric if (menu->Action() == MenuActionResult::Quit)
3042480093f4SDimitry Andric return eQuitApplication;
3043480093f4SDimitry Andric return eKeyHandled;
3044480093f4SDimitry Andric }
3045480093f4SDimitry Andric }
3046480093f4SDimitry Andric break;
3047480093f4SDimitry Andric }
3048480093f4SDimitry Andric } else if (menu_type == Menu::Type::Separator) {
3049480093f4SDimitry Andric }
3050480093f4SDimitry Andric return result;
3051480093f4SDimitry Andric }
3052480093f4SDimitry Andric
3053480093f4SDimitry Andric class Application {
3054480093f4SDimitry Andric public:
Application(FILE * in,FILE * out)3055480093f4SDimitry Andric Application(FILE *in, FILE *out)
3056480093f4SDimitry Andric : m_window_sp(), m_screen(nullptr), m_in(in), m_out(out) {}
3057480093f4SDimitry Andric
~Application()3058480093f4SDimitry Andric ~Application() {
3059480093f4SDimitry Andric m_window_delegates.clear();
3060480093f4SDimitry Andric m_window_sp.reset();
3061480093f4SDimitry Andric if (m_screen) {
3062480093f4SDimitry Andric ::delscreen(m_screen);
3063480093f4SDimitry Andric m_screen = nullptr;
3064480093f4SDimitry Andric }
3065480093f4SDimitry Andric }
3066480093f4SDimitry Andric
Initialize()3067480093f4SDimitry Andric void Initialize() {
3068480093f4SDimitry Andric m_screen = ::newterm(nullptr, m_out, m_in);
3069480093f4SDimitry Andric ::start_color();
3070480093f4SDimitry Andric ::curs_set(0);
3071480093f4SDimitry Andric ::noecho();
3072480093f4SDimitry Andric ::keypad(stdscr, TRUE);
3073480093f4SDimitry Andric }
3074480093f4SDimitry Andric
Terminate()3075480093f4SDimitry Andric void Terminate() { ::endwin(); }
3076480093f4SDimitry Andric
Run(Debugger & debugger)3077480093f4SDimitry Andric void Run(Debugger &debugger) {
3078480093f4SDimitry Andric bool done = false;
3079480093f4SDimitry Andric int delay_in_tenths_of_a_second = 1;
3080480093f4SDimitry Andric
3081480093f4SDimitry Andric // Alas the threading model in curses is a bit lame so we need to resort to
3082480093f4SDimitry Andric // polling every 0.5 seconds. We could poll for stdin ourselves and then
3083480093f4SDimitry Andric // pass the keys down but then we need to translate all of the escape
3084480093f4SDimitry Andric // sequences ourselves. So we resort to polling for input because we need
3085480093f4SDimitry Andric // to receive async process events while in this loop.
3086480093f4SDimitry Andric
3087480093f4SDimitry Andric halfdelay(delay_in_tenths_of_a_second); // Poll using some number of tenths
3088480093f4SDimitry Andric // of seconds seconds when calling
3089480093f4SDimitry Andric // Window::GetChar()
3090480093f4SDimitry Andric
3091480093f4SDimitry Andric ListenerSP listener_sp(
3092480093f4SDimitry Andric Listener::MakeListener("lldb.IOHandler.curses.Application"));
3093480093f4SDimitry Andric ConstString broadcaster_class_process(Process::GetStaticBroadcasterClass());
3094480093f4SDimitry Andric debugger.EnableForwardEvents(listener_sp);
3095480093f4SDimitry Andric
3096af732203SDimitry Andric m_update_screen = true;
3097480093f4SDimitry Andric #if defined(__APPLE__)
3098480093f4SDimitry Andric std::deque<int> escape_chars;
3099480093f4SDimitry Andric #endif
3100480093f4SDimitry Andric
3101480093f4SDimitry Andric while (!done) {
3102af732203SDimitry Andric if (m_update_screen) {
3103480093f4SDimitry Andric m_window_sp->Draw(false);
3104480093f4SDimitry Andric // All windows should be calling Window::DeferredRefresh() instead of
3105480093f4SDimitry Andric // Window::Refresh() so we can do a single update and avoid any screen
3106480093f4SDimitry Andric // blinking
3107480093f4SDimitry Andric update_panels();
3108480093f4SDimitry Andric
3109480093f4SDimitry Andric // Cursor hiding isn't working on MacOSX, so hide it in the top left
3110480093f4SDimitry Andric // corner
3111480093f4SDimitry Andric m_window_sp->MoveCursor(0, 0);
3112480093f4SDimitry Andric
3113480093f4SDimitry Andric doupdate();
3114af732203SDimitry Andric m_update_screen = false;
3115480093f4SDimitry Andric }
3116480093f4SDimitry Andric
3117480093f4SDimitry Andric #if defined(__APPLE__)
3118480093f4SDimitry Andric // Terminal.app doesn't map its function keys correctly, F1-F4 default
3119480093f4SDimitry Andric // to: \033OP, \033OQ, \033OR, \033OS, so lets take care of this here if
3120480093f4SDimitry Andric // possible
3121480093f4SDimitry Andric int ch;
3122480093f4SDimitry Andric if (escape_chars.empty())
3123480093f4SDimitry Andric ch = m_window_sp->GetChar();
3124480093f4SDimitry Andric else {
3125480093f4SDimitry Andric ch = escape_chars.front();
3126480093f4SDimitry Andric escape_chars.pop_front();
3127480093f4SDimitry Andric }
3128480093f4SDimitry Andric if (ch == KEY_ESCAPE) {
3129480093f4SDimitry Andric int ch2 = m_window_sp->GetChar();
3130480093f4SDimitry Andric if (ch2 == 'O') {
3131480093f4SDimitry Andric int ch3 = m_window_sp->GetChar();
3132480093f4SDimitry Andric switch (ch3) {
3133480093f4SDimitry Andric case 'P':
3134480093f4SDimitry Andric ch = KEY_F(1);
3135480093f4SDimitry Andric break;
3136480093f4SDimitry Andric case 'Q':
3137480093f4SDimitry Andric ch = KEY_F(2);
3138480093f4SDimitry Andric break;
3139480093f4SDimitry Andric case 'R':
3140480093f4SDimitry Andric ch = KEY_F(3);
3141480093f4SDimitry Andric break;
3142480093f4SDimitry Andric case 'S':
3143480093f4SDimitry Andric ch = KEY_F(4);
3144480093f4SDimitry Andric break;
3145480093f4SDimitry Andric default:
3146480093f4SDimitry Andric escape_chars.push_back(ch2);
3147480093f4SDimitry Andric if (ch3 != -1)
3148480093f4SDimitry Andric escape_chars.push_back(ch3);
3149480093f4SDimitry Andric break;
3150480093f4SDimitry Andric }
3151480093f4SDimitry Andric } else if (ch2 != -1)
3152480093f4SDimitry Andric escape_chars.push_back(ch2);
3153480093f4SDimitry Andric }
3154480093f4SDimitry Andric #else
3155480093f4SDimitry Andric int ch = m_window_sp->GetChar();
3156480093f4SDimitry Andric
3157480093f4SDimitry Andric #endif
3158480093f4SDimitry Andric if (ch == -1) {
3159480093f4SDimitry Andric if (feof(m_in) || ferror(m_in)) {
3160480093f4SDimitry Andric done = true;
3161480093f4SDimitry Andric } else {
3162480093f4SDimitry Andric // Just a timeout from using halfdelay(), check for events
3163480093f4SDimitry Andric EventSP event_sp;
3164480093f4SDimitry Andric while (listener_sp->PeekAtNextEvent()) {
3165480093f4SDimitry Andric listener_sp->GetEvent(event_sp, std::chrono::seconds(0));
3166480093f4SDimitry Andric
3167480093f4SDimitry Andric if (event_sp) {
3168480093f4SDimitry Andric Broadcaster *broadcaster = event_sp->GetBroadcaster();
3169480093f4SDimitry Andric if (broadcaster) {
3170480093f4SDimitry Andric // uint32_t event_type = event_sp->GetType();
3171480093f4SDimitry Andric ConstString broadcaster_class(
3172480093f4SDimitry Andric broadcaster->GetBroadcasterClass());
3173480093f4SDimitry Andric if (broadcaster_class == broadcaster_class_process) {
3174af732203SDimitry Andric m_update_screen = true;
3175480093f4SDimitry Andric continue; // Don't get any key, just update our view
3176480093f4SDimitry Andric }
3177480093f4SDimitry Andric }
3178480093f4SDimitry Andric }
3179480093f4SDimitry Andric }
3180480093f4SDimitry Andric }
3181480093f4SDimitry Andric } else {
3182480093f4SDimitry Andric HandleCharResult key_result = m_window_sp->HandleChar(ch);
3183480093f4SDimitry Andric switch (key_result) {
3184480093f4SDimitry Andric case eKeyHandled:
3185af732203SDimitry Andric m_update_screen = true;
3186480093f4SDimitry Andric break;
3187480093f4SDimitry Andric case eKeyNotHandled:
3188af732203SDimitry Andric if (ch == 12) { // Ctrl+L, force full redraw
3189af732203SDimitry Andric redrawwin(m_window_sp->get());
3190af732203SDimitry Andric m_update_screen = true;
3191af732203SDimitry Andric }
3192480093f4SDimitry Andric break;
3193480093f4SDimitry Andric case eQuitApplication:
3194480093f4SDimitry Andric done = true;
3195480093f4SDimitry Andric break;
3196480093f4SDimitry Andric }
3197480093f4SDimitry Andric }
3198480093f4SDimitry Andric }
3199480093f4SDimitry Andric
3200480093f4SDimitry Andric debugger.CancelForwardEvents(listener_sp);
3201480093f4SDimitry Andric }
3202480093f4SDimitry Andric
GetMainWindow()3203480093f4SDimitry Andric WindowSP &GetMainWindow() {
3204480093f4SDimitry Andric if (!m_window_sp)
3205480093f4SDimitry Andric m_window_sp = std::make_shared<Window>("main", stdscr, false);
3206480093f4SDimitry Andric return m_window_sp;
3207480093f4SDimitry Andric }
3208480093f4SDimitry Andric
TerminalSizeChanged()3209af732203SDimitry Andric void TerminalSizeChanged() {
3210af732203SDimitry Andric ::endwin();
3211af732203SDimitry Andric ::refresh();
3212af732203SDimitry Andric Rect content_bounds = m_window_sp->GetFrame();
3213af732203SDimitry Andric m_window_sp->SetBounds(content_bounds);
3214af732203SDimitry Andric if (WindowSP menubar_window_sp = m_window_sp->FindSubWindow("Menubar"))
3215af732203SDimitry Andric menubar_window_sp->SetBounds(content_bounds.MakeMenuBar());
3216af732203SDimitry Andric if (WindowSP status_window_sp = m_window_sp->FindSubWindow("Status"))
3217af732203SDimitry Andric status_window_sp->SetBounds(content_bounds.MakeStatusBar());
3218af732203SDimitry Andric
3219af732203SDimitry Andric WindowSP source_window_sp = m_window_sp->FindSubWindow("Source");
3220af732203SDimitry Andric WindowSP variables_window_sp = m_window_sp->FindSubWindow("Variables");
3221af732203SDimitry Andric WindowSP registers_window_sp = m_window_sp->FindSubWindow("Registers");
3222af732203SDimitry Andric WindowSP threads_window_sp = m_window_sp->FindSubWindow("Threads");
3223af732203SDimitry Andric
3224af732203SDimitry Andric Rect threads_bounds;
3225af732203SDimitry Andric Rect source_variables_bounds;
3226af732203SDimitry Andric content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds,
3227af732203SDimitry Andric threads_bounds);
3228af732203SDimitry Andric if (threads_window_sp)
3229af732203SDimitry Andric threads_window_sp->SetBounds(threads_bounds);
3230af732203SDimitry Andric else
3231af732203SDimitry Andric source_variables_bounds = content_bounds;
3232af732203SDimitry Andric
3233af732203SDimitry Andric Rect source_bounds;
3234af732203SDimitry Andric Rect variables_registers_bounds;
3235af732203SDimitry Andric source_variables_bounds.HorizontalSplitPercentage(
3236af732203SDimitry Andric 0.70, source_bounds, variables_registers_bounds);
3237af732203SDimitry Andric if (variables_window_sp || registers_window_sp) {
3238af732203SDimitry Andric if (variables_window_sp && registers_window_sp) {
3239af732203SDimitry Andric Rect variables_bounds;
3240af732203SDimitry Andric Rect registers_bounds;
3241af732203SDimitry Andric variables_registers_bounds.VerticalSplitPercentage(
3242af732203SDimitry Andric 0.50, variables_bounds, registers_bounds);
3243af732203SDimitry Andric variables_window_sp->SetBounds(variables_bounds);
3244af732203SDimitry Andric registers_window_sp->SetBounds(registers_bounds);
3245af732203SDimitry Andric } else if (variables_window_sp) {
3246af732203SDimitry Andric variables_window_sp->SetBounds(variables_registers_bounds);
3247af732203SDimitry Andric } else {
3248af732203SDimitry Andric registers_window_sp->SetBounds(variables_registers_bounds);
3249af732203SDimitry Andric }
3250af732203SDimitry Andric } else {
3251af732203SDimitry Andric source_bounds = source_variables_bounds;
3252af732203SDimitry Andric }
3253af732203SDimitry Andric
3254af732203SDimitry Andric source_window_sp->SetBounds(source_bounds);
3255af732203SDimitry Andric
3256af732203SDimitry Andric touchwin(stdscr);
3257af732203SDimitry Andric redrawwin(m_window_sp->get());
3258af732203SDimitry Andric m_update_screen = true;
3259af732203SDimitry Andric }
3260af732203SDimitry Andric
3261480093f4SDimitry Andric protected:
3262480093f4SDimitry Andric WindowSP m_window_sp;
3263480093f4SDimitry Andric WindowDelegates m_window_delegates;
3264480093f4SDimitry Andric SCREEN *m_screen;
3265480093f4SDimitry Andric FILE *m_in;
3266480093f4SDimitry Andric FILE *m_out;
3267af732203SDimitry Andric bool m_update_screen = false;
3268480093f4SDimitry Andric };
3269480093f4SDimitry Andric
3270480093f4SDimitry Andric } // namespace curses
3271480093f4SDimitry Andric
3272480093f4SDimitry Andric using namespace curses;
3273480093f4SDimitry Andric
3274480093f4SDimitry Andric struct Row {
3275*5f7ddb14SDimitry Andric ValueObjectUpdater value;
3276480093f4SDimitry Andric Row *parent;
3277480093f4SDimitry Andric // The process stop ID when the children were calculated.
3278af732203SDimitry Andric uint32_t children_stop_id = 0;
3279af732203SDimitry Andric int row_idx = 0;
3280af732203SDimitry Andric int x = 1;
3281af732203SDimitry Andric int y = 1;
3282480093f4SDimitry Andric bool might_have_children;
3283af732203SDimitry Andric bool expanded = false;
3284af732203SDimitry Andric bool calculated_children = false;
3285480093f4SDimitry Andric std::vector<Row> children;
3286480093f4SDimitry Andric
RowRow3287480093f4SDimitry Andric Row(const ValueObjectSP &v, Row *p)
3288*5f7ddb14SDimitry Andric : value(v), parent(p),
3289af732203SDimitry Andric might_have_children(v ? v->MightHaveChildren() : false) {}
3290480093f4SDimitry Andric
GetDepthRow3291480093f4SDimitry Andric size_t GetDepth() const {
3292480093f4SDimitry Andric if (parent)
3293480093f4SDimitry Andric return 1 + parent->GetDepth();
3294480093f4SDimitry Andric return 0;
3295480093f4SDimitry Andric }
3296480093f4SDimitry Andric
ExpandRow3297480093f4SDimitry Andric void Expand() { expanded = true; }
3298480093f4SDimitry Andric
GetChildrenRow3299480093f4SDimitry Andric std::vector<Row> &GetChildren() {
3300480093f4SDimitry Andric ProcessSP process_sp = value.GetProcessSP();
3301480093f4SDimitry Andric auto stop_id = process_sp->GetStopID();
3302480093f4SDimitry Andric if (process_sp && stop_id != children_stop_id) {
3303480093f4SDimitry Andric children_stop_id = stop_id;
3304480093f4SDimitry Andric calculated_children = false;
3305480093f4SDimitry Andric }
3306480093f4SDimitry Andric if (!calculated_children) {
3307480093f4SDimitry Andric children.clear();
3308480093f4SDimitry Andric calculated_children = true;
3309480093f4SDimitry Andric ValueObjectSP valobj = value.GetSP();
3310480093f4SDimitry Andric if (valobj) {
3311480093f4SDimitry Andric const size_t num_children = valobj->GetNumChildren();
3312480093f4SDimitry Andric for (size_t i = 0; i < num_children; ++i) {
3313480093f4SDimitry Andric children.push_back(Row(valobj->GetChildAtIndex(i, true), this));
3314480093f4SDimitry Andric }
3315480093f4SDimitry Andric }
3316480093f4SDimitry Andric }
3317480093f4SDimitry Andric return children;
3318480093f4SDimitry Andric }
3319480093f4SDimitry Andric
UnexpandRow3320480093f4SDimitry Andric void Unexpand() {
3321480093f4SDimitry Andric expanded = false;
3322480093f4SDimitry Andric calculated_children = false;
3323480093f4SDimitry Andric children.clear();
3324480093f4SDimitry Andric }
3325480093f4SDimitry Andric
DrawTreeRow3326480093f4SDimitry Andric void DrawTree(Window &window) {
3327480093f4SDimitry Andric if (parent)
3328480093f4SDimitry Andric parent->DrawTreeForChild(window, this, 0);
3329480093f4SDimitry Andric
3330480093f4SDimitry Andric if (might_have_children) {
3331480093f4SDimitry Andric // It we can get UTF8 characters to work we should try to use the
3332480093f4SDimitry Andric // "symbol" UTF8 string below
3333480093f4SDimitry Andric // const char *symbol = "";
3334480093f4SDimitry Andric // if (row.expanded)
3335480093f4SDimitry Andric // symbol = "\xe2\x96\xbd ";
3336480093f4SDimitry Andric // else
3337480093f4SDimitry Andric // symbol = "\xe2\x96\xb7 ";
3338480093f4SDimitry Andric // window.PutCString (symbol);
3339480093f4SDimitry Andric
3340480093f4SDimitry Andric // The ACS_DARROW and ACS_RARROW don't look very nice they are just a 'v'
3341480093f4SDimitry Andric // or '>' character...
3342480093f4SDimitry Andric // if (expanded)
3343480093f4SDimitry Andric // window.PutChar (ACS_DARROW);
3344480093f4SDimitry Andric // else
3345480093f4SDimitry Andric // window.PutChar (ACS_RARROW);
3346480093f4SDimitry Andric // Since we can't find any good looking right arrow/down arrow symbols,
3347480093f4SDimitry Andric // just use a diamond...
3348480093f4SDimitry Andric window.PutChar(ACS_DIAMOND);
3349480093f4SDimitry Andric window.PutChar(ACS_HLINE);
3350480093f4SDimitry Andric }
3351480093f4SDimitry Andric }
3352480093f4SDimitry Andric
DrawTreeForChildRow3353480093f4SDimitry Andric void DrawTreeForChild(Window &window, Row *child, uint32_t reverse_depth) {
3354480093f4SDimitry Andric if (parent)
3355480093f4SDimitry Andric parent->DrawTreeForChild(window, this, reverse_depth + 1);
3356480093f4SDimitry Andric
3357480093f4SDimitry Andric if (&GetChildren().back() == child) {
3358480093f4SDimitry Andric // Last child
3359480093f4SDimitry Andric if (reverse_depth == 0) {
3360480093f4SDimitry Andric window.PutChar(ACS_LLCORNER);
3361480093f4SDimitry Andric window.PutChar(ACS_HLINE);
3362480093f4SDimitry Andric } else {
3363480093f4SDimitry Andric window.PutChar(' ');
3364480093f4SDimitry Andric window.PutChar(' ');
3365480093f4SDimitry Andric }
3366480093f4SDimitry Andric } else {
3367480093f4SDimitry Andric if (reverse_depth == 0) {
3368480093f4SDimitry Andric window.PutChar(ACS_LTEE);
3369480093f4SDimitry Andric window.PutChar(ACS_HLINE);
3370480093f4SDimitry Andric } else {
3371480093f4SDimitry Andric window.PutChar(ACS_VLINE);
3372480093f4SDimitry Andric window.PutChar(' ');
3373480093f4SDimitry Andric }
3374480093f4SDimitry Andric }
3375480093f4SDimitry Andric }
3376480093f4SDimitry Andric };
3377480093f4SDimitry Andric
3378480093f4SDimitry Andric struct DisplayOptions {
3379480093f4SDimitry Andric bool show_types;
3380480093f4SDimitry Andric };
3381480093f4SDimitry Andric
3382480093f4SDimitry Andric class TreeItem;
3383480093f4SDimitry Andric
3384480093f4SDimitry Andric class TreeDelegate {
3385480093f4SDimitry Andric public:
3386480093f4SDimitry Andric TreeDelegate() = default;
3387480093f4SDimitry Andric virtual ~TreeDelegate() = default;
3388480093f4SDimitry Andric
3389480093f4SDimitry Andric virtual void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) = 0;
3390480093f4SDimitry Andric virtual void TreeDelegateGenerateChildren(TreeItem &item) = 0;
TreeDelegateUpdateSelection(TreeItem & root,int & selection_index,TreeItem * & selected_item)3391*5f7ddb14SDimitry Andric virtual void TreeDelegateUpdateSelection(TreeItem &root, int &selection_index,
3392*5f7ddb14SDimitry Andric TreeItem *&selected_item) {
3393*5f7ddb14SDimitry Andric return;
3394*5f7ddb14SDimitry Andric }
3395480093f4SDimitry Andric virtual bool TreeDelegateItemSelected(
3396480093f4SDimitry Andric TreeItem &item) = 0; // Return true if we need to update views
TreeDelegateExpandRootByDefault()3397*5f7ddb14SDimitry Andric virtual bool TreeDelegateExpandRootByDefault() { return false; }
3398480093f4SDimitry Andric };
3399480093f4SDimitry Andric
3400480093f4SDimitry Andric typedef std::shared_ptr<TreeDelegate> TreeDelegateSP;
3401480093f4SDimitry Andric
3402480093f4SDimitry Andric class TreeItem {
3403480093f4SDimitry Andric public:
TreeItem(TreeItem * parent,TreeDelegate & delegate,bool might_have_children)3404480093f4SDimitry Andric TreeItem(TreeItem *parent, TreeDelegate &delegate, bool might_have_children)
3405480093f4SDimitry Andric : m_parent(parent), m_delegate(delegate), m_user_data(nullptr),
3406480093f4SDimitry Andric m_identifier(0), m_row_idx(-1), m_children(),
3407*5f7ddb14SDimitry Andric m_might_have_children(might_have_children), m_is_expanded(false) {
3408*5f7ddb14SDimitry Andric if (m_parent == nullptr)
3409*5f7ddb14SDimitry Andric m_is_expanded = m_delegate.TreeDelegateExpandRootByDefault();
3410*5f7ddb14SDimitry Andric }
3411480093f4SDimitry Andric
operator =(const TreeItem & rhs)3412480093f4SDimitry Andric TreeItem &operator=(const TreeItem &rhs) {
3413480093f4SDimitry Andric if (this != &rhs) {
3414480093f4SDimitry Andric m_parent = rhs.m_parent;
3415480093f4SDimitry Andric m_delegate = rhs.m_delegate;
3416480093f4SDimitry Andric m_user_data = rhs.m_user_data;
3417480093f4SDimitry Andric m_identifier = rhs.m_identifier;
3418480093f4SDimitry Andric m_row_idx = rhs.m_row_idx;
3419480093f4SDimitry Andric m_children = rhs.m_children;
3420480093f4SDimitry Andric m_might_have_children = rhs.m_might_have_children;
3421480093f4SDimitry Andric m_is_expanded = rhs.m_is_expanded;
3422480093f4SDimitry Andric }
3423480093f4SDimitry Andric return *this;
3424480093f4SDimitry Andric }
3425480093f4SDimitry Andric
3426480093f4SDimitry Andric TreeItem(const TreeItem &) = default;
3427480093f4SDimitry Andric
GetDepth() const3428480093f4SDimitry Andric size_t GetDepth() const {
3429480093f4SDimitry Andric if (m_parent)
3430480093f4SDimitry Andric return 1 + m_parent->GetDepth();
3431480093f4SDimitry Andric return 0;
3432480093f4SDimitry Andric }
3433480093f4SDimitry Andric
GetRowIndex() const3434480093f4SDimitry Andric int GetRowIndex() const { return m_row_idx; }
3435480093f4SDimitry Andric
ClearChildren()3436480093f4SDimitry Andric void ClearChildren() { m_children.clear(); }
3437480093f4SDimitry Andric
Resize(size_t n,const TreeItem & t)3438480093f4SDimitry Andric void Resize(size_t n, const TreeItem &t) { m_children.resize(n, t); }
3439480093f4SDimitry Andric
operator [](size_t i)3440480093f4SDimitry Andric TreeItem &operator[](size_t i) { return m_children[i]; }
3441480093f4SDimitry Andric
SetRowIndex(int row_idx)3442480093f4SDimitry Andric void SetRowIndex(int row_idx) { m_row_idx = row_idx; }
3443480093f4SDimitry Andric
GetNumChildren()3444480093f4SDimitry Andric size_t GetNumChildren() {
3445480093f4SDimitry Andric m_delegate.TreeDelegateGenerateChildren(*this);
3446480093f4SDimitry Andric return m_children.size();
3447480093f4SDimitry Andric }
3448480093f4SDimitry Andric
ItemWasSelected()3449480093f4SDimitry Andric void ItemWasSelected() { m_delegate.TreeDelegateItemSelected(*this); }
3450480093f4SDimitry Andric
CalculateRowIndexes(int & row_idx)3451480093f4SDimitry Andric void CalculateRowIndexes(int &row_idx) {
3452480093f4SDimitry Andric SetRowIndex(row_idx);
3453480093f4SDimitry Andric ++row_idx;
3454480093f4SDimitry Andric
3455480093f4SDimitry Andric const bool expanded = IsExpanded();
3456480093f4SDimitry Andric
3457480093f4SDimitry Andric // The root item must calculate its children, or we must calculate the
3458480093f4SDimitry Andric // number of children if the item is expanded
3459480093f4SDimitry Andric if (m_parent == nullptr || expanded)
3460480093f4SDimitry Andric GetNumChildren();
3461480093f4SDimitry Andric
3462480093f4SDimitry Andric for (auto &item : m_children) {
3463480093f4SDimitry Andric if (expanded)
3464480093f4SDimitry Andric item.CalculateRowIndexes(row_idx);
3465480093f4SDimitry Andric else
3466480093f4SDimitry Andric item.SetRowIndex(-1);
3467480093f4SDimitry Andric }
3468480093f4SDimitry Andric }
3469480093f4SDimitry Andric
GetParent()3470480093f4SDimitry Andric TreeItem *GetParent() { return m_parent; }
3471480093f4SDimitry Andric
IsExpanded() const3472480093f4SDimitry Andric bool IsExpanded() const { return m_is_expanded; }
3473480093f4SDimitry Andric
Expand()3474480093f4SDimitry Andric void Expand() { m_is_expanded = true; }
3475480093f4SDimitry Andric
Unexpand()3476480093f4SDimitry Andric void Unexpand() { m_is_expanded = false; }
3477480093f4SDimitry Andric
Draw(Window & window,const int first_visible_row,const uint32_t selected_row_idx,int & row_idx,int & num_rows_left)3478480093f4SDimitry Andric bool Draw(Window &window, const int first_visible_row,
3479480093f4SDimitry Andric const uint32_t selected_row_idx, int &row_idx, int &num_rows_left) {
3480480093f4SDimitry Andric if (num_rows_left <= 0)
3481480093f4SDimitry Andric return false;
3482480093f4SDimitry Andric
3483480093f4SDimitry Andric if (m_row_idx >= first_visible_row) {
3484480093f4SDimitry Andric window.MoveCursor(2, row_idx + 1);
3485480093f4SDimitry Andric
3486480093f4SDimitry Andric if (m_parent)
3487480093f4SDimitry Andric m_parent->DrawTreeForChild(window, this, 0);
3488480093f4SDimitry Andric
3489480093f4SDimitry Andric if (m_might_have_children) {
3490480093f4SDimitry Andric // It we can get UTF8 characters to work we should try to use the
3491480093f4SDimitry Andric // "symbol" UTF8 string below
3492480093f4SDimitry Andric // const char *symbol = "";
3493480093f4SDimitry Andric // if (row.expanded)
3494480093f4SDimitry Andric // symbol = "\xe2\x96\xbd ";
3495480093f4SDimitry Andric // else
3496480093f4SDimitry Andric // symbol = "\xe2\x96\xb7 ";
3497480093f4SDimitry Andric // window.PutCString (symbol);
3498480093f4SDimitry Andric
3499480093f4SDimitry Andric // The ACS_DARROW and ACS_RARROW don't look very nice they are just a
3500480093f4SDimitry Andric // 'v' or '>' character...
3501480093f4SDimitry Andric // if (expanded)
3502480093f4SDimitry Andric // window.PutChar (ACS_DARROW);
3503480093f4SDimitry Andric // else
3504480093f4SDimitry Andric // window.PutChar (ACS_RARROW);
3505480093f4SDimitry Andric // Since we can't find any good looking right arrow/down arrow symbols,
3506480093f4SDimitry Andric // just use a diamond...
3507480093f4SDimitry Andric window.PutChar(ACS_DIAMOND);
3508480093f4SDimitry Andric window.PutChar(ACS_HLINE);
3509480093f4SDimitry Andric }
3510480093f4SDimitry Andric bool highlight = (selected_row_idx == static_cast<size_t>(m_row_idx)) &&
3511480093f4SDimitry Andric window.IsActive();
3512480093f4SDimitry Andric
3513480093f4SDimitry Andric if (highlight)
3514480093f4SDimitry Andric window.AttributeOn(A_REVERSE);
3515480093f4SDimitry Andric
3516480093f4SDimitry Andric m_delegate.TreeDelegateDrawTreeItem(*this, window);
3517480093f4SDimitry Andric
3518480093f4SDimitry Andric if (highlight)
3519480093f4SDimitry Andric window.AttributeOff(A_REVERSE);
3520480093f4SDimitry Andric ++row_idx;
3521480093f4SDimitry Andric --num_rows_left;
3522480093f4SDimitry Andric }
3523480093f4SDimitry Andric
3524480093f4SDimitry Andric if (num_rows_left <= 0)
3525480093f4SDimitry Andric return false; // We are done drawing...
3526480093f4SDimitry Andric
3527480093f4SDimitry Andric if (IsExpanded()) {
3528480093f4SDimitry Andric for (auto &item : m_children) {
3529480093f4SDimitry Andric // If we displayed all the rows and item.Draw() returns false we are
3530480093f4SDimitry Andric // done drawing and can exit this for loop
3531480093f4SDimitry Andric if (!item.Draw(window, first_visible_row, selected_row_idx, row_idx,
3532480093f4SDimitry Andric num_rows_left))
3533480093f4SDimitry Andric break;
3534480093f4SDimitry Andric }
3535480093f4SDimitry Andric }
3536480093f4SDimitry Andric return num_rows_left >= 0; // Return true if not done drawing yet
3537480093f4SDimitry Andric }
3538480093f4SDimitry Andric
DrawTreeForChild(Window & window,TreeItem * child,uint32_t reverse_depth)3539480093f4SDimitry Andric void DrawTreeForChild(Window &window, TreeItem *child,
3540480093f4SDimitry Andric uint32_t reverse_depth) {
3541480093f4SDimitry Andric if (m_parent)
3542480093f4SDimitry Andric m_parent->DrawTreeForChild(window, this, reverse_depth + 1);
3543480093f4SDimitry Andric
3544480093f4SDimitry Andric if (&m_children.back() == child) {
3545480093f4SDimitry Andric // Last child
3546480093f4SDimitry Andric if (reverse_depth == 0) {
3547480093f4SDimitry Andric window.PutChar(ACS_LLCORNER);
3548480093f4SDimitry Andric window.PutChar(ACS_HLINE);
3549480093f4SDimitry Andric } else {
3550480093f4SDimitry Andric window.PutChar(' ');
3551480093f4SDimitry Andric window.PutChar(' ');
3552480093f4SDimitry Andric }
3553480093f4SDimitry Andric } else {
3554480093f4SDimitry Andric if (reverse_depth == 0) {
3555480093f4SDimitry Andric window.PutChar(ACS_LTEE);
3556480093f4SDimitry Andric window.PutChar(ACS_HLINE);
3557480093f4SDimitry Andric } else {
3558480093f4SDimitry Andric window.PutChar(ACS_VLINE);
3559480093f4SDimitry Andric window.PutChar(' ');
3560480093f4SDimitry Andric }
3561480093f4SDimitry Andric }
3562480093f4SDimitry Andric }
3563480093f4SDimitry Andric
GetItemForRowIndex(uint32_t row_idx)3564480093f4SDimitry Andric TreeItem *GetItemForRowIndex(uint32_t row_idx) {
3565480093f4SDimitry Andric if (static_cast<uint32_t>(m_row_idx) == row_idx)
3566480093f4SDimitry Andric return this;
3567480093f4SDimitry Andric if (m_children.empty())
3568480093f4SDimitry Andric return nullptr;
3569480093f4SDimitry Andric if (IsExpanded()) {
3570480093f4SDimitry Andric for (auto &item : m_children) {
3571480093f4SDimitry Andric TreeItem *selected_item_ptr = item.GetItemForRowIndex(row_idx);
3572480093f4SDimitry Andric if (selected_item_ptr)
3573480093f4SDimitry Andric return selected_item_ptr;
3574480093f4SDimitry Andric }
3575480093f4SDimitry Andric }
3576480093f4SDimitry Andric return nullptr;
3577480093f4SDimitry Andric }
3578480093f4SDimitry Andric
GetUserData() const3579480093f4SDimitry Andric void *GetUserData() const { return m_user_data; }
3580480093f4SDimitry Andric
SetUserData(void * user_data)3581480093f4SDimitry Andric void SetUserData(void *user_data) { m_user_data = user_data; }
3582480093f4SDimitry Andric
GetIdentifier() const3583480093f4SDimitry Andric uint64_t GetIdentifier() const { return m_identifier; }
3584480093f4SDimitry Andric
SetIdentifier(uint64_t identifier)3585480093f4SDimitry Andric void SetIdentifier(uint64_t identifier) { m_identifier = identifier; }
3586480093f4SDimitry Andric
SetMightHaveChildren(bool b)3587480093f4SDimitry Andric void SetMightHaveChildren(bool b) { m_might_have_children = b; }
3588480093f4SDimitry Andric
3589480093f4SDimitry Andric protected:
3590480093f4SDimitry Andric TreeItem *m_parent;
3591480093f4SDimitry Andric TreeDelegate &m_delegate;
3592480093f4SDimitry Andric void *m_user_data;
3593480093f4SDimitry Andric uint64_t m_identifier;
3594480093f4SDimitry Andric int m_row_idx; // Zero based visible row index, -1 if not visible or for the
3595480093f4SDimitry Andric // root item
3596480093f4SDimitry Andric std::vector<TreeItem> m_children;
3597480093f4SDimitry Andric bool m_might_have_children;
3598480093f4SDimitry Andric bool m_is_expanded;
3599480093f4SDimitry Andric };
3600480093f4SDimitry Andric
3601480093f4SDimitry Andric class TreeWindowDelegate : public WindowDelegate {
3602480093f4SDimitry Andric public:
TreeWindowDelegate(Debugger & debugger,const TreeDelegateSP & delegate_sp)3603480093f4SDimitry Andric TreeWindowDelegate(Debugger &debugger, const TreeDelegateSP &delegate_sp)
3604480093f4SDimitry Andric : m_debugger(debugger), m_delegate_sp(delegate_sp),
3605480093f4SDimitry Andric m_root(nullptr, *delegate_sp, true), m_selected_item(nullptr),
3606480093f4SDimitry Andric m_num_rows(0), m_selected_row_idx(0), m_first_visible_row(0),
3607480093f4SDimitry Andric m_min_x(0), m_min_y(0), m_max_x(0), m_max_y(0) {}
3608480093f4SDimitry Andric
NumVisibleRows() const3609480093f4SDimitry Andric int NumVisibleRows() const { return m_max_y - m_min_y; }
3610480093f4SDimitry Andric
WindowDelegateDraw(Window & window,bool force)3611480093f4SDimitry Andric bool WindowDelegateDraw(Window &window, bool force) override {
3612480093f4SDimitry Andric ExecutionContext exe_ctx(
3613480093f4SDimitry Andric m_debugger.GetCommandInterpreter().GetExecutionContext());
3614480093f4SDimitry Andric Process *process = exe_ctx.GetProcessPtr();
3615480093f4SDimitry Andric
3616480093f4SDimitry Andric bool display_content = false;
3617480093f4SDimitry Andric if (process) {
3618480093f4SDimitry Andric StateType state = process->GetState();
3619480093f4SDimitry Andric if (StateIsStoppedState(state, true)) {
3620480093f4SDimitry Andric // We are stopped, so it is ok to
3621480093f4SDimitry Andric display_content = true;
3622480093f4SDimitry Andric } else if (StateIsRunningState(state)) {
3623480093f4SDimitry Andric return true; // Don't do any updating when we are running
3624480093f4SDimitry Andric }
3625480093f4SDimitry Andric }
3626480093f4SDimitry Andric
3627480093f4SDimitry Andric m_min_x = 2;
3628480093f4SDimitry Andric m_min_y = 1;
3629480093f4SDimitry Andric m_max_x = window.GetWidth() - 1;
3630480093f4SDimitry Andric m_max_y = window.GetHeight() - 1;
3631480093f4SDimitry Andric
3632480093f4SDimitry Andric window.Erase();
3633480093f4SDimitry Andric window.DrawTitleBox(window.GetName());
3634480093f4SDimitry Andric
3635480093f4SDimitry Andric if (display_content) {
3636480093f4SDimitry Andric const int num_visible_rows = NumVisibleRows();
3637480093f4SDimitry Andric m_num_rows = 0;
3638480093f4SDimitry Andric m_root.CalculateRowIndexes(m_num_rows);
3639*5f7ddb14SDimitry Andric m_delegate_sp->TreeDelegateUpdateSelection(m_root, m_selected_row_idx,
3640*5f7ddb14SDimitry Andric m_selected_item);
3641480093f4SDimitry Andric
3642480093f4SDimitry Andric // If we unexpanded while having something selected our total number of
3643480093f4SDimitry Andric // rows is less than the num visible rows, then make sure we show all the
3644480093f4SDimitry Andric // rows by setting the first visible row accordingly.
3645480093f4SDimitry Andric if (m_first_visible_row > 0 && m_num_rows < num_visible_rows)
3646480093f4SDimitry Andric m_first_visible_row = 0;
3647480093f4SDimitry Andric
3648480093f4SDimitry Andric // Make sure the selected row is always visible
3649480093f4SDimitry Andric if (m_selected_row_idx < m_first_visible_row)
3650480093f4SDimitry Andric m_first_visible_row = m_selected_row_idx;
3651480093f4SDimitry Andric else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx)
3652480093f4SDimitry Andric m_first_visible_row = m_selected_row_idx - num_visible_rows + 1;
3653480093f4SDimitry Andric
3654480093f4SDimitry Andric int row_idx = 0;
3655480093f4SDimitry Andric int num_rows_left = num_visible_rows;
3656480093f4SDimitry Andric m_root.Draw(window, m_first_visible_row, m_selected_row_idx, row_idx,
3657480093f4SDimitry Andric num_rows_left);
3658480093f4SDimitry Andric // Get the selected row
3659480093f4SDimitry Andric m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
3660480093f4SDimitry Andric } else {
3661480093f4SDimitry Andric m_selected_item = nullptr;
3662480093f4SDimitry Andric }
3663480093f4SDimitry Andric
3664480093f4SDimitry Andric return true; // Drawing handled
3665480093f4SDimitry Andric }
3666480093f4SDimitry Andric
WindowDelegateGetHelpText()3667480093f4SDimitry Andric const char *WindowDelegateGetHelpText() override {
3668480093f4SDimitry Andric return "Thread window keyboard shortcuts:";
3669480093f4SDimitry Andric }
3670480093f4SDimitry Andric
WindowDelegateGetKeyHelp()3671480093f4SDimitry Andric KeyHelp *WindowDelegateGetKeyHelp() override {
3672480093f4SDimitry Andric static curses::KeyHelp g_source_view_key_help[] = {
3673480093f4SDimitry Andric {KEY_UP, "Select previous item"},
3674480093f4SDimitry Andric {KEY_DOWN, "Select next item"},
3675480093f4SDimitry Andric {KEY_RIGHT, "Expand the selected item"},
3676480093f4SDimitry Andric {KEY_LEFT,
3677480093f4SDimitry Andric "Unexpand the selected item or select parent if not expanded"},
3678480093f4SDimitry Andric {KEY_PPAGE, "Page up"},
3679480093f4SDimitry Andric {KEY_NPAGE, "Page down"},
3680480093f4SDimitry Andric {'h', "Show help dialog"},
3681480093f4SDimitry Andric {' ', "Toggle item expansion"},
3682480093f4SDimitry Andric {',', "Page up"},
3683480093f4SDimitry Andric {'.', "Page down"},
3684480093f4SDimitry Andric {'\0', nullptr}};
3685480093f4SDimitry Andric return g_source_view_key_help;
3686480093f4SDimitry Andric }
3687480093f4SDimitry Andric
WindowDelegateHandleChar(Window & window,int c)3688480093f4SDimitry Andric HandleCharResult WindowDelegateHandleChar(Window &window, int c) override {
3689480093f4SDimitry Andric switch (c) {
3690480093f4SDimitry Andric case ',':
3691480093f4SDimitry Andric case KEY_PPAGE:
3692480093f4SDimitry Andric // Page up key
3693480093f4SDimitry Andric if (m_first_visible_row > 0) {
3694480093f4SDimitry Andric if (m_first_visible_row > m_max_y)
3695480093f4SDimitry Andric m_first_visible_row -= m_max_y;
3696480093f4SDimitry Andric else
3697480093f4SDimitry Andric m_first_visible_row = 0;
3698480093f4SDimitry Andric m_selected_row_idx = m_first_visible_row;
3699480093f4SDimitry Andric m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
3700480093f4SDimitry Andric if (m_selected_item)
3701480093f4SDimitry Andric m_selected_item->ItemWasSelected();
3702480093f4SDimitry Andric }
3703480093f4SDimitry Andric return eKeyHandled;
3704480093f4SDimitry Andric
3705480093f4SDimitry Andric case '.':
3706480093f4SDimitry Andric case KEY_NPAGE:
3707480093f4SDimitry Andric // Page down key
3708480093f4SDimitry Andric if (m_num_rows > m_max_y) {
3709480093f4SDimitry Andric if (m_first_visible_row + m_max_y < m_num_rows) {
3710480093f4SDimitry Andric m_first_visible_row += m_max_y;
3711480093f4SDimitry Andric m_selected_row_idx = m_first_visible_row;
3712480093f4SDimitry Andric m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
3713480093f4SDimitry Andric if (m_selected_item)
3714480093f4SDimitry Andric m_selected_item->ItemWasSelected();
3715480093f4SDimitry Andric }
3716480093f4SDimitry Andric }
3717480093f4SDimitry Andric return eKeyHandled;
3718480093f4SDimitry Andric
3719480093f4SDimitry Andric case KEY_UP:
3720480093f4SDimitry Andric if (m_selected_row_idx > 0) {
3721480093f4SDimitry Andric --m_selected_row_idx;
3722480093f4SDimitry Andric m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
3723480093f4SDimitry Andric if (m_selected_item)
3724480093f4SDimitry Andric m_selected_item->ItemWasSelected();
3725480093f4SDimitry Andric }
3726480093f4SDimitry Andric return eKeyHandled;
3727480093f4SDimitry Andric
3728480093f4SDimitry Andric case KEY_DOWN:
3729480093f4SDimitry Andric if (m_selected_row_idx + 1 < m_num_rows) {
3730480093f4SDimitry Andric ++m_selected_row_idx;
3731480093f4SDimitry Andric m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
3732480093f4SDimitry Andric if (m_selected_item)
3733480093f4SDimitry Andric m_selected_item->ItemWasSelected();
3734480093f4SDimitry Andric }
3735480093f4SDimitry Andric return eKeyHandled;
3736480093f4SDimitry Andric
3737480093f4SDimitry Andric case KEY_RIGHT:
3738480093f4SDimitry Andric if (m_selected_item) {
3739480093f4SDimitry Andric if (!m_selected_item->IsExpanded())
3740480093f4SDimitry Andric m_selected_item->Expand();
3741480093f4SDimitry Andric }
3742480093f4SDimitry Andric return eKeyHandled;
3743480093f4SDimitry Andric
3744480093f4SDimitry Andric case KEY_LEFT:
3745480093f4SDimitry Andric if (m_selected_item) {
3746480093f4SDimitry Andric if (m_selected_item->IsExpanded())
3747480093f4SDimitry Andric m_selected_item->Unexpand();
3748480093f4SDimitry Andric else if (m_selected_item->GetParent()) {
3749480093f4SDimitry Andric m_selected_row_idx = m_selected_item->GetParent()->GetRowIndex();
3750480093f4SDimitry Andric m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
3751480093f4SDimitry Andric if (m_selected_item)
3752480093f4SDimitry Andric m_selected_item->ItemWasSelected();
3753480093f4SDimitry Andric }
3754480093f4SDimitry Andric }
3755480093f4SDimitry Andric return eKeyHandled;
3756480093f4SDimitry Andric
3757480093f4SDimitry Andric case ' ':
3758480093f4SDimitry Andric // Toggle expansion state when SPACE is pressed
3759480093f4SDimitry Andric if (m_selected_item) {
3760480093f4SDimitry Andric if (m_selected_item->IsExpanded())
3761480093f4SDimitry Andric m_selected_item->Unexpand();
3762480093f4SDimitry Andric else
3763480093f4SDimitry Andric m_selected_item->Expand();
3764480093f4SDimitry Andric }
3765480093f4SDimitry Andric return eKeyHandled;
3766480093f4SDimitry Andric
3767480093f4SDimitry Andric case 'h':
3768480093f4SDimitry Andric window.CreateHelpSubwindow();
3769480093f4SDimitry Andric return eKeyHandled;
3770480093f4SDimitry Andric
3771480093f4SDimitry Andric default:
3772480093f4SDimitry Andric break;
3773480093f4SDimitry Andric }
3774480093f4SDimitry Andric return eKeyNotHandled;
3775480093f4SDimitry Andric }
3776480093f4SDimitry Andric
3777480093f4SDimitry Andric protected:
3778480093f4SDimitry Andric Debugger &m_debugger;
3779480093f4SDimitry Andric TreeDelegateSP m_delegate_sp;
3780480093f4SDimitry Andric TreeItem m_root;
3781480093f4SDimitry Andric TreeItem *m_selected_item;
3782480093f4SDimitry Andric int m_num_rows;
3783480093f4SDimitry Andric int m_selected_row_idx;
3784480093f4SDimitry Andric int m_first_visible_row;
3785480093f4SDimitry Andric int m_min_x;
3786480093f4SDimitry Andric int m_min_y;
3787480093f4SDimitry Andric int m_max_x;
3788480093f4SDimitry Andric int m_max_y;
3789480093f4SDimitry Andric };
3790480093f4SDimitry Andric
3791480093f4SDimitry Andric class FrameTreeDelegate : public TreeDelegate {
3792480093f4SDimitry Andric public:
FrameTreeDelegate()3793480093f4SDimitry Andric FrameTreeDelegate() : TreeDelegate() {
3794480093f4SDimitry Andric FormatEntity::Parse(
3795480093f4SDimitry Andric "frame #${frame.index}: {${function.name}${function.pc-offset}}}",
3796480093f4SDimitry Andric m_format);
3797480093f4SDimitry Andric }
3798480093f4SDimitry Andric
3799480093f4SDimitry Andric ~FrameTreeDelegate() override = default;
3800480093f4SDimitry Andric
TreeDelegateDrawTreeItem(TreeItem & item,Window & window)3801480093f4SDimitry Andric void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
3802480093f4SDimitry Andric Thread *thread = (Thread *)item.GetUserData();
3803480093f4SDimitry Andric if (thread) {
3804480093f4SDimitry Andric const uint64_t frame_idx = item.GetIdentifier();
3805480093f4SDimitry Andric StackFrameSP frame_sp = thread->GetStackFrameAtIndex(frame_idx);
3806480093f4SDimitry Andric if (frame_sp) {
3807480093f4SDimitry Andric StreamString strm;
3808480093f4SDimitry Andric const SymbolContext &sc =
3809480093f4SDimitry Andric frame_sp->GetSymbolContext(eSymbolContextEverything);
3810480093f4SDimitry Andric ExecutionContext exe_ctx(frame_sp);
3811480093f4SDimitry Andric if (FormatEntity::Format(m_format, strm, &sc, &exe_ctx, nullptr,
3812480093f4SDimitry Andric nullptr, false, false)) {
3813480093f4SDimitry Andric int right_pad = 1;
3814af732203SDimitry Andric window.PutCStringTruncated(right_pad, strm.GetString().str().c_str());
3815480093f4SDimitry Andric }
3816480093f4SDimitry Andric }
3817480093f4SDimitry Andric }
3818480093f4SDimitry Andric }
3819480093f4SDimitry Andric
TreeDelegateGenerateChildren(TreeItem & item)3820480093f4SDimitry Andric void TreeDelegateGenerateChildren(TreeItem &item) override {
3821480093f4SDimitry Andric // No children for frames yet...
3822480093f4SDimitry Andric }
3823480093f4SDimitry Andric
TreeDelegateItemSelected(TreeItem & item)3824480093f4SDimitry Andric bool TreeDelegateItemSelected(TreeItem &item) override {
3825480093f4SDimitry Andric Thread *thread = (Thread *)item.GetUserData();
3826480093f4SDimitry Andric if (thread) {
3827480093f4SDimitry Andric thread->GetProcess()->GetThreadList().SetSelectedThreadByID(
3828480093f4SDimitry Andric thread->GetID());
3829480093f4SDimitry Andric const uint64_t frame_idx = item.GetIdentifier();
3830480093f4SDimitry Andric thread->SetSelectedFrameByIndex(frame_idx);
3831480093f4SDimitry Andric return true;
3832480093f4SDimitry Andric }
3833480093f4SDimitry Andric return false;
3834480093f4SDimitry Andric }
3835480093f4SDimitry Andric
3836480093f4SDimitry Andric protected:
3837480093f4SDimitry Andric FormatEntity::Entry m_format;
3838480093f4SDimitry Andric };
3839480093f4SDimitry Andric
3840480093f4SDimitry Andric class ThreadTreeDelegate : public TreeDelegate {
3841480093f4SDimitry Andric public:
ThreadTreeDelegate(Debugger & debugger)3842480093f4SDimitry Andric ThreadTreeDelegate(Debugger &debugger)
3843480093f4SDimitry Andric : TreeDelegate(), m_debugger(debugger), m_tid(LLDB_INVALID_THREAD_ID),
3844480093f4SDimitry Andric m_stop_id(UINT32_MAX) {
3845480093f4SDimitry Andric FormatEntity::Parse("thread #${thread.index}: tid = ${thread.id}{, stop "
3846480093f4SDimitry Andric "reason = ${thread.stop-reason}}",
3847480093f4SDimitry Andric m_format);
3848480093f4SDimitry Andric }
3849480093f4SDimitry Andric
3850480093f4SDimitry Andric ~ThreadTreeDelegate() override = default;
3851480093f4SDimitry Andric
GetProcess()3852480093f4SDimitry Andric ProcessSP GetProcess() {
3853480093f4SDimitry Andric return m_debugger.GetCommandInterpreter()
3854480093f4SDimitry Andric .GetExecutionContext()
3855480093f4SDimitry Andric .GetProcessSP();
3856480093f4SDimitry Andric }
3857480093f4SDimitry Andric
GetThread(const TreeItem & item)3858480093f4SDimitry Andric ThreadSP GetThread(const TreeItem &item) {
3859480093f4SDimitry Andric ProcessSP process_sp = GetProcess();
3860480093f4SDimitry Andric if (process_sp)
3861480093f4SDimitry Andric return process_sp->GetThreadList().FindThreadByID(item.GetIdentifier());
3862480093f4SDimitry Andric return ThreadSP();
3863480093f4SDimitry Andric }
3864480093f4SDimitry Andric
TreeDelegateDrawTreeItem(TreeItem & item,Window & window)3865480093f4SDimitry Andric void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
3866480093f4SDimitry Andric ThreadSP thread_sp = GetThread(item);
3867480093f4SDimitry Andric if (thread_sp) {
3868480093f4SDimitry Andric StreamString strm;
3869480093f4SDimitry Andric ExecutionContext exe_ctx(thread_sp);
3870480093f4SDimitry Andric if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr,
3871480093f4SDimitry Andric nullptr, false, false)) {
3872480093f4SDimitry Andric int right_pad = 1;
3873af732203SDimitry Andric window.PutCStringTruncated(right_pad, strm.GetString().str().c_str());
3874480093f4SDimitry Andric }
3875480093f4SDimitry Andric }
3876480093f4SDimitry Andric }
3877480093f4SDimitry Andric
TreeDelegateGenerateChildren(TreeItem & item)3878480093f4SDimitry Andric void TreeDelegateGenerateChildren(TreeItem &item) override {
3879480093f4SDimitry Andric ProcessSP process_sp = GetProcess();
3880480093f4SDimitry Andric if (process_sp && process_sp->IsAlive()) {
3881480093f4SDimitry Andric StateType state = process_sp->GetState();
3882480093f4SDimitry Andric if (StateIsStoppedState(state, true)) {
3883480093f4SDimitry Andric ThreadSP thread_sp = GetThread(item);
3884480093f4SDimitry Andric if (thread_sp) {
3885480093f4SDimitry Andric if (m_stop_id == process_sp->GetStopID() &&
3886480093f4SDimitry Andric thread_sp->GetID() == m_tid)
3887480093f4SDimitry Andric return; // Children are already up to date
3888480093f4SDimitry Andric if (!m_frame_delegate_sp) {
3889480093f4SDimitry Andric // Always expand the thread item the first time we show it
3890480093f4SDimitry Andric m_frame_delegate_sp = std::make_shared<FrameTreeDelegate>();
3891480093f4SDimitry Andric }
3892480093f4SDimitry Andric
3893480093f4SDimitry Andric m_stop_id = process_sp->GetStopID();
3894480093f4SDimitry Andric m_tid = thread_sp->GetID();
3895480093f4SDimitry Andric
3896480093f4SDimitry Andric TreeItem t(&item, *m_frame_delegate_sp, false);
3897480093f4SDimitry Andric size_t num_frames = thread_sp->GetStackFrameCount();
3898480093f4SDimitry Andric item.Resize(num_frames, t);
3899480093f4SDimitry Andric for (size_t i = 0; i < num_frames; ++i) {
3900480093f4SDimitry Andric item[i].SetUserData(thread_sp.get());
3901480093f4SDimitry Andric item[i].SetIdentifier(i);
3902480093f4SDimitry Andric }
3903480093f4SDimitry Andric }
3904480093f4SDimitry Andric return;
3905480093f4SDimitry Andric }
3906480093f4SDimitry Andric }
3907480093f4SDimitry Andric item.ClearChildren();
3908480093f4SDimitry Andric }
3909480093f4SDimitry Andric
TreeDelegateItemSelected(TreeItem & item)3910480093f4SDimitry Andric bool TreeDelegateItemSelected(TreeItem &item) override {
3911480093f4SDimitry Andric ProcessSP process_sp = GetProcess();
3912480093f4SDimitry Andric if (process_sp && process_sp->IsAlive()) {
3913480093f4SDimitry Andric StateType state = process_sp->GetState();
3914480093f4SDimitry Andric if (StateIsStoppedState(state, true)) {
3915480093f4SDimitry Andric ThreadSP thread_sp = GetThread(item);
3916480093f4SDimitry Andric if (thread_sp) {
3917480093f4SDimitry Andric ThreadList &thread_list = thread_sp->GetProcess()->GetThreadList();
3918480093f4SDimitry Andric std::lock_guard<std::recursive_mutex> guard(thread_list.GetMutex());
3919480093f4SDimitry Andric ThreadSP selected_thread_sp = thread_list.GetSelectedThread();
3920480093f4SDimitry Andric if (selected_thread_sp->GetID() != thread_sp->GetID()) {
3921480093f4SDimitry Andric thread_list.SetSelectedThreadByID(thread_sp->GetID());
3922480093f4SDimitry Andric return true;
3923480093f4SDimitry Andric }
3924480093f4SDimitry Andric }
3925480093f4SDimitry Andric }
3926480093f4SDimitry Andric }
3927480093f4SDimitry Andric return false;
3928480093f4SDimitry Andric }
3929480093f4SDimitry Andric
3930480093f4SDimitry Andric protected:
3931480093f4SDimitry Andric Debugger &m_debugger;
3932480093f4SDimitry Andric std::shared_ptr<FrameTreeDelegate> m_frame_delegate_sp;
3933480093f4SDimitry Andric lldb::user_id_t m_tid;
3934480093f4SDimitry Andric uint32_t m_stop_id;
3935480093f4SDimitry Andric FormatEntity::Entry m_format;
3936480093f4SDimitry Andric };
3937480093f4SDimitry Andric
3938480093f4SDimitry Andric class ThreadsTreeDelegate : public TreeDelegate {
3939480093f4SDimitry Andric public:
ThreadsTreeDelegate(Debugger & debugger)3940480093f4SDimitry Andric ThreadsTreeDelegate(Debugger &debugger)
3941480093f4SDimitry Andric : TreeDelegate(), m_thread_delegate_sp(), m_debugger(debugger),
3942*5f7ddb14SDimitry Andric m_stop_id(UINT32_MAX), m_update_selection(false) {
3943480093f4SDimitry Andric FormatEntity::Parse("process ${process.id}{, name = ${process.name}}",
3944480093f4SDimitry Andric m_format);
3945480093f4SDimitry Andric }
3946480093f4SDimitry Andric
3947480093f4SDimitry Andric ~ThreadsTreeDelegate() override = default;
3948480093f4SDimitry Andric
GetProcess()3949480093f4SDimitry Andric ProcessSP GetProcess() {
3950480093f4SDimitry Andric return m_debugger.GetCommandInterpreter()
3951480093f4SDimitry Andric .GetExecutionContext()
3952480093f4SDimitry Andric .GetProcessSP();
3953480093f4SDimitry Andric }
3954480093f4SDimitry Andric
TreeDelegateDrawTreeItem(TreeItem & item,Window & window)3955480093f4SDimitry Andric void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
3956480093f4SDimitry Andric ProcessSP process_sp = GetProcess();
3957480093f4SDimitry Andric if (process_sp && process_sp->IsAlive()) {
3958480093f4SDimitry Andric StreamString strm;
3959480093f4SDimitry Andric ExecutionContext exe_ctx(process_sp);
3960480093f4SDimitry Andric if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr,
3961480093f4SDimitry Andric nullptr, false, false)) {
3962480093f4SDimitry Andric int right_pad = 1;
3963af732203SDimitry Andric window.PutCStringTruncated(right_pad, strm.GetString().str().c_str());
3964480093f4SDimitry Andric }
3965480093f4SDimitry Andric }
3966480093f4SDimitry Andric }
3967480093f4SDimitry Andric
TreeDelegateGenerateChildren(TreeItem & item)3968480093f4SDimitry Andric void TreeDelegateGenerateChildren(TreeItem &item) override {
3969480093f4SDimitry Andric ProcessSP process_sp = GetProcess();
3970*5f7ddb14SDimitry Andric m_update_selection = false;
3971480093f4SDimitry Andric if (process_sp && process_sp->IsAlive()) {
3972480093f4SDimitry Andric StateType state = process_sp->GetState();
3973480093f4SDimitry Andric if (StateIsStoppedState(state, true)) {
3974480093f4SDimitry Andric const uint32_t stop_id = process_sp->GetStopID();
3975480093f4SDimitry Andric if (m_stop_id == stop_id)
3976480093f4SDimitry Andric return; // Children are already up to date
3977480093f4SDimitry Andric
3978480093f4SDimitry Andric m_stop_id = stop_id;
3979*5f7ddb14SDimitry Andric m_update_selection = true;
3980480093f4SDimitry Andric
3981480093f4SDimitry Andric if (!m_thread_delegate_sp) {
3982480093f4SDimitry Andric // Always expand the thread item the first time we show it
3983480093f4SDimitry Andric // item.Expand();
3984480093f4SDimitry Andric m_thread_delegate_sp =
3985480093f4SDimitry Andric std::make_shared<ThreadTreeDelegate>(m_debugger);
3986480093f4SDimitry Andric }
3987480093f4SDimitry Andric
3988480093f4SDimitry Andric TreeItem t(&item, *m_thread_delegate_sp, false);
3989480093f4SDimitry Andric ThreadList &threads = process_sp->GetThreadList();
3990480093f4SDimitry Andric std::lock_guard<std::recursive_mutex> guard(threads.GetMutex());
3991*5f7ddb14SDimitry Andric ThreadSP selected_thread = threads.GetSelectedThread();
3992480093f4SDimitry Andric size_t num_threads = threads.GetSize();
3993480093f4SDimitry Andric item.Resize(num_threads, t);
3994480093f4SDimitry Andric for (size_t i = 0; i < num_threads; ++i) {
3995*5f7ddb14SDimitry Andric ThreadSP thread = threads.GetThreadAtIndex(i);
3996*5f7ddb14SDimitry Andric item[i].SetIdentifier(thread->GetID());
3997480093f4SDimitry Andric item[i].SetMightHaveChildren(true);
3998*5f7ddb14SDimitry Andric if (selected_thread->GetID() == thread->GetID())
3999*5f7ddb14SDimitry Andric item[i].Expand();
4000480093f4SDimitry Andric }
4001480093f4SDimitry Andric return;
4002480093f4SDimitry Andric }
4003480093f4SDimitry Andric }
4004480093f4SDimitry Andric item.ClearChildren();
4005480093f4SDimitry Andric }
4006480093f4SDimitry Andric
TreeDelegateUpdateSelection(TreeItem & root,int & selection_index,TreeItem * & selected_item)4007*5f7ddb14SDimitry Andric void TreeDelegateUpdateSelection(TreeItem &root, int &selection_index,
4008*5f7ddb14SDimitry Andric TreeItem *&selected_item) override {
4009*5f7ddb14SDimitry Andric if (!m_update_selection)
4010*5f7ddb14SDimitry Andric return;
4011*5f7ddb14SDimitry Andric
4012*5f7ddb14SDimitry Andric ProcessSP process_sp = GetProcess();
4013*5f7ddb14SDimitry Andric if (!(process_sp && process_sp->IsAlive()))
4014*5f7ddb14SDimitry Andric return;
4015*5f7ddb14SDimitry Andric
4016*5f7ddb14SDimitry Andric StateType state = process_sp->GetState();
4017*5f7ddb14SDimitry Andric if (!StateIsStoppedState(state, true))
4018*5f7ddb14SDimitry Andric return;
4019*5f7ddb14SDimitry Andric
4020*5f7ddb14SDimitry Andric ThreadList &threads = process_sp->GetThreadList();
4021*5f7ddb14SDimitry Andric std::lock_guard<std::recursive_mutex> guard(threads.GetMutex());
4022*5f7ddb14SDimitry Andric ThreadSP selected_thread = threads.GetSelectedThread();
4023*5f7ddb14SDimitry Andric size_t num_threads = threads.GetSize();
4024*5f7ddb14SDimitry Andric for (size_t i = 0; i < num_threads; ++i) {
4025*5f7ddb14SDimitry Andric ThreadSP thread = threads.GetThreadAtIndex(i);
4026*5f7ddb14SDimitry Andric if (selected_thread->GetID() == thread->GetID()) {
4027*5f7ddb14SDimitry Andric selected_item = &root[i][thread->GetSelectedFrameIndex()];
4028*5f7ddb14SDimitry Andric selection_index = selected_item->GetRowIndex();
4029*5f7ddb14SDimitry Andric return;
4030*5f7ddb14SDimitry Andric }
4031*5f7ddb14SDimitry Andric }
4032*5f7ddb14SDimitry Andric }
4033*5f7ddb14SDimitry Andric
TreeDelegateItemSelected(TreeItem & item)4034480093f4SDimitry Andric bool TreeDelegateItemSelected(TreeItem &item) override { return false; }
4035480093f4SDimitry Andric
TreeDelegateExpandRootByDefault()4036*5f7ddb14SDimitry Andric bool TreeDelegateExpandRootByDefault() override { return true; }
4037*5f7ddb14SDimitry Andric
4038480093f4SDimitry Andric protected:
4039480093f4SDimitry Andric std::shared_ptr<ThreadTreeDelegate> m_thread_delegate_sp;
4040480093f4SDimitry Andric Debugger &m_debugger;
4041480093f4SDimitry Andric uint32_t m_stop_id;
4042*5f7ddb14SDimitry Andric bool m_update_selection;
4043480093f4SDimitry Andric FormatEntity::Entry m_format;
4044480093f4SDimitry Andric };
4045480093f4SDimitry Andric
4046480093f4SDimitry Andric class ValueObjectListDelegate : public WindowDelegate {
4047480093f4SDimitry Andric public:
ValueObjectListDelegate()4048*5f7ddb14SDimitry Andric ValueObjectListDelegate() : m_rows() {}
4049480093f4SDimitry Andric
ValueObjectListDelegate(ValueObjectList & valobj_list)4050480093f4SDimitry Andric ValueObjectListDelegate(ValueObjectList &valobj_list)
4051480093f4SDimitry Andric : m_rows(), m_selected_row(nullptr), m_selected_row_idx(0),
4052480093f4SDimitry Andric m_first_visible_row(0), m_num_rows(0), m_max_x(0), m_max_y(0) {
4053480093f4SDimitry Andric SetValues(valobj_list);
4054480093f4SDimitry Andric }
4055480093f4SDimitry Andric
4056480093f4SDimitry Andric ~ValueObjectListDelegate() override = default;
4057480093f4SDimitry Andric
SetValues(ValueObjectList & valobj_list)4058480093f4SDimitry Andric void SetValues(ValueObjectList &valobj_list) {
4059480093f4SDimitry Andric m_selected_row = nullptr;
4060480093f4SDimitry Andric m_selected_row_idx = 0;
4061480093f4SDimitry Andric m_first_visible_row = 0;
4062480093f4SDimitry Andric m_num_rows = 0;
4063480093f4SDimitry Andric m_rows.clear();
4064480093f4SDimitry Andric for (auto &valobj_sp : valobj_list.GetObjects())
4065480093f4SDimitry Andric m_rows.push_back(Row(valobj_sp, nullptr));
4066480093f4SDimitry Andric }
4067480093f4SDimitry Andric
WindowDelegateDraw(Window & window,bool force)4068480093f4SDimitry Andric bool WindowDelegateDraw(Window &window, bool force) override {
4069480093f4SDimitry Andric m_num_rows = 0;
4070480093f4SDimitry Andric m_min_x = 2;
4071480093f4SDimitry Andric m_min_y = 1;
4072480093f4SDimitry Andric m_max_x = window.GetWidth() - 1;
4073480093f4SDimitry Andric m_max_y = window.GetHeight() - 1;
4074480093f4SDimitry Andric
4075480093f4SDimitry Andric window.Erase();
4076480093f4SDimitry Andric window.DrawTitleBox(window.GetName());
4077480093f4SDimitry Andric
4078480093f4SDimitry Andric const int num_visible_rows = NumVisibleRows();
4079480093f4SDimitry Andric const int num_rows = CalculateTotalNumberRows(m_rows);
4080480093f4SDimitry Andric
4081480093f4SDimitry Andric // If we unexpanded while having something selected our total number of
4082480093f4SDimitry Andric // rows is less than the num visible rows, then make sure we show all the
4083480093f4SDimitry Andric // rows by setting the first visible row accordingly.
4084480093f4SDimitry Andric if (m_first_visible_row > 0 && num_rows < num_visible_rows)
4085480093f4SDimitry Andric m_first_visible_row = 0;
4086480093f4SDimitry Andric
4087480093f4SDimitry Andric // Make sure the selected row is always visible
4088480093f4SDimitry Andric if (m_selected_row_idx < m_first_visible_row)
4089480093f4SDimitry Andric m_first_visible_row = m_selected_row_idx;
4090480093f4SDimitry Andric else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx)
4091480093f4SDimitry Andric m_first_visible_row = m_selected_row_idx - num_visible_rows + 1;
4092480093f4SDimitry Andric
4093480093f4SDimitry Andric DisplayRows(window, m_rows, g_options);
4094480093f4SDimitry Andric
4095480093f4SDimitry Andric // Get the selected row
4096480093f4SDimitry Andric m_selected_row = GetRowForRowIndex(m_selected_row_idx);
4097480093f4SDimitry Andric // Keep the cursor on the selected row so the highlight and the cursor are
4098480093f4SDimitry Andric // always on the same line
4099480093f4SDimitry Andric if (m_selected_row)
4100480093f4SDimitry Andric window.MoveCursor(m_selected_row->x, m_selected_row->y);
4101480093f4SDimitry Andric
4102480093f4SDimitry Andric return true; // Drawing handled
4103480093f4SDimitry Andric }
4104480093f4SDimitry Andric
WindowDelegateGetKeyHelp()4105480093f4SDimitry Andric KeyHelp *WindowDelegateGetKeyHelp() override {
4106480093f4SDimitry Andric static curses::KeyHelp g_source_view_key_help[] = {
4107480093f4SDimitry Andric {KEY_UP, "Select previous item"},
4108480093f4SDimitry Andric {KEY_DOWN, "Select next item"},
4109480093f4SDimitry Andric {KEY_RIGHT, "Expand selected item"},
4110480093f4SDimitry Andric {KEY_LEFT, "Unexpand selected item or select parent if not expanded"},
4111480093f4SDimitry Andric {KEY_PPAGE, "Page up"},
4112480093f4SDimitry Andric {KEY_NPAGE, "Page down"},
4113480093f4SDimitry Andric {'A', "Format as annotated address"},
4114480093f4SDimitry Andric {'b', "Format as binary"},
4115480093f4SDimitry Andric {'B', "Format as hex bytes with ASCII"},
4116480093f4SDimitry Andric {'c', "Format as character"},
4117480093f4SDimitry Andric {'d', "Format as a signed integer"},
4118480093f4SDimitry Andric {'D', "Format selected value using the default format for the type"},
4119480093f4SDimitry Andric {'f', "Format as float"},
4120480093f4SDimitry Andric {'h', "Show help dialog"},
4121480093f4SDimitry Andric {'i', "Format as instructions"},
4122480093f4SDimitry Andric {'o', "Format as octal"},
4123480093f4SDimitry Andric {'p', "Format as pointer"},
4124480093f4SDimitry Andric {'s', "Format as C string"},
4125480093f4SDimitry Andric {'t', "Toggle showing/hiding type names"},
4126480093f4SDimitry Andric {'u', "Format as an unsigned integer"},
4127480093f4SDimitry Andric {'x', "Format as hex"},
4128480093f4SDimitry Andric {'X', "Format as uppercase hex"},
4129480093f4SDimitry Andric {' ', "Toggle item expansion"},
4130480093f4SDimitry Andric {',', "Page up"},
4131480093f4SDimitry Andric {'.', "Page down"},
4132480093f4SDimitry Andric {'\0', nullptr}};
4133480093f4SDimitry Andric return g_source_view_key_help;
4134480093f4SDimitry Andric }
4135480093f4SDimitry Andric
WindowDelegateHandleChar(Window & window,int c)4136480093f4SDimitry Andric HandleCharResult WindowDelegateHandleChar(Window &window, int c) override {
4137480093f4SDimitry Andric switch (c) {
4138480093f4SDimitry Andric case 'x':
4139480093f4SDimitry Andric case 'X':
4140480093f4SDimitry Andric case 'o':
4141480093f4SDimitry Andric case 's':
4142480093f4SDimitry Andric case 'u':
4143480093f4SDimitry Andric case 'd':
4144480093f4SDimitry Andric case 'D':
4145480093f4SDimitry Andric case 'i':
4146480093f4SDimitry Andric case 'A':
4147480093f4SDimitry Andric case 'p':
4148480093f4SDimitry Andric case 'c':
4149480093f4SDimitry Andric case 'b':
4150480093f4SDimitry Andric case 'B':
4151480093f4SDimitry Andric case 'f':
4152480093f4SDimitry Andric // Change the format for the currently selected item
4153480093f4SDimitry Andric if (m_selected_row) {
4154480093f4SDimitry Andric auto valobj_sp = m_selected_row->value.GetSP();
4155480093f4SDimitry Andric if (valobj_sp)
4156480093f4SDimitry Andric valobj_sp->SetFormat(FormatForChar(c));
4157480093f4SDimitry Andric }
4158480093f4SDimitry Andric return eKeyHandled;
4159480093f4SDimitry Andric
4160480093f4SDimitry Andric case 't':
4161480093f4SDimitry Andric // Toggle showing type names
4162480093f4SDimitry Andric g_options.show_types = !g_options.show_types;
4163480093f4SDimitry Andric return eKeyHandled;
4164480093f4SDimitry Andric
4165480093f4SDimitry Andric case ',':
4166480093f4SDimitry Andric case KEY_PPAGE:
4167480093f4SDimitry Andric // Page up key
4168480093f4SDimitry Andric if (m_first_visible_row > 0) {
4169480093f4SDimitry Andric if (static_cast<int>(m_first_visible_row) > m_max_y)
4170480093f4SDimitry Andric m_first_visible_row -= m_max_y;
4171480093f4SDimitry Andric else
4172480093f4SDimitry Andric m_first_visible_row = 0;
4173480093f4SDimitry Andric m_selected_row_idx = m_first_visible_row;
4174480093f4SDimitry Andric }
4175480093f4SDimitry Andric return eKeyHandled;
4176480093f4SDimitry Andric
4177480093f4SDimitry Andric case '.':
4178480093f4SDimitry Andric case KEY_NPAGE:
4179480093f4SDimitry Andric // Page down key
4180480093f4SDimitry Andric if (m_num_rows > static_cast<size_t>(m_max_y)) {
4181480093f4SDimitry Andric if (m_first_visible_row + m_max_y < m_num_rows) {
4182480093f4SDimitry Andric m_first_visible_row += m_max_y;
4183480093f4SDimitry Andric m_selected_row_idx = m_first_visible_row;
4184480093f4SDimitry Andric }
4185480093f4SDimitry Andric }
4186480093f4SDimitry Andric return eKeyHandled;
4187480093f4SDimitry Andric
4188480093f4SDimitry Andric case KEY_UP:
4189480093f4SDimitry Andric if (m_selected_row_idx > 0)
4190480093f4SDimitry Andric --m_selected_row_idx;
4191480093f4SDimitry Andric return eKeyHandled;
4192480093f4SDimitry Andric
4193480093f4SDimitry Andric case KEY_DOWN:
4194480093f4SDimitry Andric if (m_selected_row_idx + 1 < m_num_rows)
4195480093f4SDimitry Andric ++m_selected_row_idx;
4196480093f4SDimitry Andric return eKeyHandled;
4197480093f4SDimitry Andric
4198480093f4SDimitry Andric case KEY_RIGHT:
4199480093f4SDimitry Andric if (m_selected_row) {
4200480093f4SDimitry Andric if (!m_selected_row->expanded)
4201480093f4SDimitry Andric m_selected_row->Expand();
4202480093f4SDimitry Andric }
4203480093f4SDimitry Andric return eKeyHandled;
4204480093f4SDimitry Andric
4205480093f4SDimitry Andric case KEY_LEFT:
4206480093f4SDimitry Andric if (m_selected_row) {
4207480093f4SDimitry Andric if (m_selected_row->expanded)
4208480093f4SDimitry Andric m_selected_row->Unexpand();
4209480093f4SDimitry Andric else if (m_selected_row->parent)
4210480093f4SDimitry Andric m_selected_row_idx = m_selected_row->parent->row_idx;
4211480093f4SDimitry Andric }
4212480093f4SDimitry Andric return eKeyHandled;
4213480093f4SDimitry Andric
4214480093f4SDimitry Andric case ' ':
4215480093f4SDimitry Andric // Toggle expansion state when SPACE is pressed
4216480093f4SDimitry Andric if (m_selected_row) {
4217480093f4SDimitry Andric if (m_selected_row->expanded)
4218480093f4SDimitry Andric m_selected_row->Unexpand();
4219480093f4SDimitry Andric else
4220480093f4SDimitry Andric m_selected_row->Expand();
4221480093f4SDimitry Andric }
4222480093f4SDimitry Andric return eKeyHandled;
4223480093f4SDimitry Andric
4224480093f4SDimitry Andric case 'h':
4225480093f4SDimitry Andric window.CreateHelpSubwindow();
4226480093f4SDimitry Andric return eKeyHandled;
4227480093f4SDimitry Andric
4228480093f4SDimitry Andric default:
4229480093f4SDimitry Andric break;
4230480093f4SDimitry Andric }
4231480093f4SDimitry Andric return eKeyNotHandled;
4232480093f4SDimitry Andric }
4233480093f4SDimitry Andric
4234480093f4SDimitry Andric protected:
4235480093f4SDimitry Andric std::vector<Row> m_rows;
4236*5f7ddb14SDimitry Andric Row *m_selected_row = nullptr;
4237*5f7ddb14SDimitry Andric uint32_t m_selected_row_idx = 0;
4238*5f7ddb14SDimitry Andric uint32_t m_first_visible_row = 0;
4239*5f7ddb14SDimitry Andric uint32_t m_num_rows = 0;
4240480093f4SDimitry Andric int m_min_x;
4241480093f4SDimitry Andric int m_min_y;
4242*5f7ddb14SDimitry Andric int m_max_x = 0;
4243*5f7ddb14SDimitry Andric int m_max_y = 0;
4244480093f4SDimitry Andric
FormatForChar(int c)4245480093f4SDimitry Andric static Format FormatForChar(int c) {
4246480093f4SDimitry Andric switch (c) {
4247480093f4SDimitry Andric case 'x':
4248480093f4SDimitry Andric return eFormatHex;
4249480093f4SDimitry Andric case 'X':
4250480093f4SDimitry Andric return eFormatHexUppercase;
4251480093f4SDimitry Andric case 'o':
4252480093f4SDimitry Andric return eFormatOctal;
4253480093f4SDimitry Andric case 's':
4254480093f4SDimitry Andric return eFormatCString;
4255480093f4SDimitry Andric case 'u':
4256480093f4SDimitry Andric return eFormatUnsigned;
4257480093f4SDimitry Andric case 'd':
4258480093f4SDimitry Andric return eFormatDecimal;
4259480093f4SDimitry Andric case 'D':
4260480093f4SDimitry Andric return eFormatDefault;
4261480093f4SDimitry Andric case 'i':
4262480093f4SDimitry Andric return eFormatInstruction;
4263480093f4SDimitry Andric case 'A':
4264480093f4SDimitry Andric return eFormatAddressInfo;
4265480093f4SDimitry Andric case 'p':
4266480093f4SDimitry Andric return eFormatPointer;
4267480093f4SDimitry Andric case 'c':
4268480093f4SDimitry Andric return eFormatChar;
4269480093f4SDimitry Andric case 'b':
4270480093f4SDimitry Andric return eFormatBinary;
4271480093f4SDimitry Andric case 'B':
4272480093f4SDimitry Andric return eFormatBytesWithASCII;
4273480093f4SDimitry Andric case 'f':
4274480093f4SDimitry Andric return eFormatFloat;
4275480093f4SDimitry Andric }
4276480093f4SDimitry Andric return eFormatDefault;
4277480093f4SDimitry Andric }
4278480093f4SDimitry Andric
DisplayRowObject(Window & window,Row & row,DisplayOptions & options,bool highlight,bool last_child)4279480093f4SDimitry Andric bool DisplayRowObject(Window &window, Row &row, DisplayOptions &options,
4280480093f4SDimitry Andric bool highlight, bool last_child) {
4281480093f4SDimitry Andric ValueObject *valobj = row.value.GetSP().get();
4282480093f4SDimitry Andric
4283480093f4SDimitry Andric if (valobj == nullptr)
4284480093f4SDimitry Andric return false;
4285480093f4SDimitry Andric
4286480093f4SDimitry Andric const char *type_name =
4287480093f4SDimitry Andric options.show_types ? valobj->GetTypeName().GetCString() : nullptr;
4288480093f4SDimitry Andric const char *name = valobj->GetName().GetCString();
4289480093f4SDimitry Andric const char *value = valobj->GetValueAsCString();
4290480093f4SDimitry Andric const char *summary = valobj->GetSummaryAsCString();
4291480093f4SDimitry Andric
4292480093f4SDimitry Andric window.MoveCursor(row.x, row.y);
4293480093f4SDimitry Andric
4294480093f4SDimitry Andric row.DrawTree(window);
4295480093f4SDimitry Andric
4296480093f4SDimitry Andric if (highlight)
4297480093f4SDimitry Andric window.AttributeOn(A_REVERSE);
4298480093f4SDimitry Andric
4299480093f4SDimitry Andric if (type_name && type_name[0])
4300af732203SDimitry Andric window.PrintfTruncated(1, "(%s) ", type_name);
4301480093f4SDimitry Andric
4302480093f4SDimitry Andric if (name && name[0])
4303af732203SDimitry Andric window.PutCStringTruncated(1, name);
4304480093f4SDimitry Andric
4305480093f4SDimitry Andric attr_t changd_attr = 0;
4306480093f4SDimitry Andric if (valobj->GetValueDidChange())
4307af732203SDimitry Andric changd_attr = COLOR_PAIR(RedOnBlack) | A_BOLD;
4308480093f4SDimitry Andric
4309480093f4SDimitry Andric if (value && value[0]) {
4310af732203SDimitry Andric window.PutCStringTruncated(1, " = ");
4311480093f4SDimitry Andric if (changd_attr)
4312480093f4SDimitry Andric window.AttributeOn(changd_attr);
4313af732203SDimitry Andric window.PutCStringTruncated(1, value);
4314480093f4SDimitry Andric if (changd_attr)
4315480093f4SDimitry Andric window.AttributeOff(changd_attr);
4316480093f4SDimitry Andric }
4317480093f4SDimitry Andric
4318480093f4SDimitry Andric if (summary && summary[0]) {
4319af732203SDimitry Andric window.PutCStringTruncated(1, " ");
4320480093f4SDimitry Andric if (changd_attr)
4321480093f4SDimitry Andric window.AttributeOn(changd_attr);
4322af732203SDimitry Andric window.PutCStringTruncated(1, summary);
4323480093f4SDimitry Andric if (changd_attr)
4324480093f4SDimitry Andric window.AttributeOff(changd_attr);
4325480093f4SDimitry Andric }
4326480093f4SDimitry Andric
4327480093f4SDimitry Andric if (highlight)
4328480093f4SDimitry Andric window.AttributeOff(A_REVERSE);
4329480093f4SDimitry Andric
4330480093f4SDimitry Andric return true;
4331480093f4SDimitry Andric }
4332480093f4SDimitry Andric
DisplayRows(Window & window,std::vector<Row> & rows,DisplayOptions & options)4333480093f4SDimitry Andric void DisplayRows(Window &window, std::vector<Row> &rows,
4334480093f4SDimitry Andric DisplayOptions &options) {
4335480093f4SDimitry Andric // > 0x25B7
4336480093f4SDimitry Andric // \/ 0x25BD
4337480093f4SDimitry Andric
4338480093f4SDimitry Andric bool window_is_active = window.IsActive();
4339480093f4SDimitry Andric for (auto &row : rows) {
4340480093f4SDimitry Andric const bool last_child = row.parent && &rows[rows.size() - 1] == &row;
4341480093f4SDimitry Andric // Save the row index in each Row structure
4342480093f4SDimitry Andric row.row_idx = m_num_rows;
4343480093f4SDimitry Andric if ((m_num_rows >= m_first_visible_row) &&
4344480093f4SDimitry Andric ((m_num_rows - m_first_visible_row) <
4345480093f4SDimitry Andric static_cast<size_t>(NumVisibleRows()))) {
4346480093f4SDimitry Andric row.x = m_min_x;
4347480093f4SDimitry Andric row.y = m_num_rows - m_first_visible_row + 1;
4348480093f4SDimitry Andric if (DisplayRowObject(window, row, options,
4349480093f4SDimitry Andric window_is_active &&
4350480093f4SDimitry Andric m_num_rows == m_selected_row_idx,
4351480093f4SDimitry Andric last_child)) {
4352480093f4SDimitry Andric ++m_num_rows;
4353480093f4SDimitry Andric } else {
4354480093f4SDimitry Andric row.x = 0;
4355480093f4SDimitry Andric row.y = 0;
4356480093f4SDimitry Andric }
4357480093f4SDimitry Andric } else {
4358480093f4SDimitry Andric row.x = 0;
4359480093f4SDimitry Andric row.y = 0;
4360480093f4SDimitry Andric ++m_num_rows;
4361480093f4SDimitry Andric }
4362480093f4SDimitry Andric
4363480093f4SDimitry Andric auto &children = row.GetChildren();
4364480093f4SDimitry Andric if (row.expanded && !children.empty()) {
4365480093f4SDimitry Andric DisplayRows(window, children, options);
4366480093f4SDimitry Andric }
4367480093f4SDimitry Andric }
4368480093f4SDimitry Andric }
4369480093f4SDimitry Andric
CalculateTotalNumberRows(std::vector<Row> & rows)4370480093f4SDimitry Andric int CalculateTotalNumberRows(std::vector<Row> &rows) {
4371480093f4SDimitry Andric int row_count = 0;
4372480093f4SDimitry Andric for (auto &row : rows) {
4373480093f4SDimitry Andric ++row_count;
4374480093f4SDimitry Andric if (row.expanded)
4375480093f4SDimitry Andric row_count += CalculateTotalNumberRows(row.GetChildren());
4376480093f4SDimitry Andric }
4377480093f4SDimitry Andric return row_count;
4378480093f4SDimitry Andric }
4379480093f4SDimitry Andric
GetRowForRowIndexImpl(std::vector<Row> & rows,size_t & row_index)4380480093f4SDimitry Andric static Row *GetRowForRowIndexImpl(std::vector<Row> &rows, size_t &row_index) {
4381480093f4SDimitry Andric for (auto &row : rows) {
4382480093f4SDimitry Andric if (row_index == 0)
4383480093f4SDimitry Andric return &row;
4384480093f4SDimitry Andric else {
4385480093f4SDimitry Andric --row_index;
4386480093f4SDimitry Andric auto &children = row.GetChildren();
4387480093f4SDimitry Andric if (row.expanded && !children.empty()) {
4388480093f4SDimitry Andric Row *result = GetRowForRowIndexImpl(children, row_index);
4389480093f4SDimitry Andric if (result)
4390480093f4SDimitry Andric return result;
4391480093f4SDimitry Andric }
4392480093f4SDimitry Andric }
4393480093f4SDimitry Andric }
4394480093f4SDimitry Andric return nullptr;
4395480093f4SDimitry Andric }
4396480093f4SDimitry Andric
GetRowForRowIndex(size_t row_index)4397480093f4SDimitry Andric Row *GetRowForRowIndex(size_t row_index) {
4398480093f4SDimitry Andric return GetRowForRowIndexImpl(m_rows, row_index);
4399480093f4SDimitry Andric }
4400480093f4SDimitry Andric
NumVisibleRows() const4401480093f4SDimitry Andric int NumVisibleRows() const { return m_max_y - m_min_y; }
4402480093f4SDimitry Andric
4403480093f4SDimitry Andric static DisplayOptions g_options;
4404480093f4SDimitry Andric };
4405480093f4SDimitry Andric
4406480093f4SDimitry Andric class FrameVariablesWindowDelegate : public ValueObjectListDelegate {
4407480093f4SDimitry Andric public:
FrameVariablesWindowDelegate(Debugger & debugger)4408480093f4SDimitry Andric FrameVariablesWindowDelegate(Debugger &debugger)
4409480093f4SDimitry Andric : ValueObjectListDelegate(), m_debugger(debugger),
4410480093f4SDimitry Andric m_frame_block(nullptr) {}
4411480093f4SDimitry Andric
4412480093f4SDimitry Andric ~FrameVariablesWindowDelegate() override = default;
4413480093f4SDimitry Andric
WindowDelegateGetHelpText()4414480093f4SDimitry Andric const char *WindowDelegateGetHelpText() override {
4415480093f4SDimitry Andric return "Frame variable window keyboard shortcuts:";
4416480093f4SDimitry Andric }
4417480093f4SDimitry Andric
WindowDelegateDraw(Window & window,bool force)4418480093f4SDimitry Andric bool WindowDelegateDraw(Window &window, bool force) override {
4419480093f4SDimitry Andric ExecutionContext exe_ctx(
4420480093f4SDimitry Andric m_debugger.GetCommandInterpreter().GetExecutionContext());
4421480093f4SDimitry Andric Process *process = exe_ctx.GetProcessPtr();
4422480093f4SDimitry Andric Block *frame_block = nullptr;
4423480093f4SDimitry Andric StackFrame *frame = nullptr;
4424480093f4SDimitry Andric
4425480093f4SDimitry Andric if (process) {
4426480093f4SDimitry Andric StateType state = process->GetState();
4427480093f4SDimitry Andric if (StateIsStoppedState(state, true)) {
4428480093f4SDimitry Andric frame = exe_ctx.GetFramePtr();
4429480093f4SDimitry Andric if (frame)
4430480093f4SDimitry Andric frame_block = frame->GetFrameBlock();
4431480093f4SDimitry Andric } else if (StateIsRunningState(state)) {
4432480093f4SDimitry Andric return true; // Don't do any updating when we are running
4433480093f4SDimitry Andric }
4434480093f4SDimitry Andric }
4435480093f4SDimitry Andric
4436480093f4SDimitry Andric ValueObjectList local_values;
4437480093f4SDimitry Andric if (frame_block) {
4438480093f4SDimitry Andric // Only update the variables if they have changed
4439480093f4SDimitry Andric if (m_frame_block != frame_block) {
4440480093f4SDimitry Andric m_frame_block = frame_block;
4441480093f4SDimitry Andric
4442480093f4SDimitry Andric VariableList *locals = frame->GetVariableList(true);
4443480093f4SDimitry Andric if (locals) {
4444480093f4SDimitry Andric const DynamicValueType use_dynamic = eDynamicDontRunTarget;
4445480093f4SDimitry Andric for (const VariableSP &local_sp : *locals) {
4446480093f4SDimitry Andric ValueObjectSP value_sp =
4447480093f4SDimitry Andric frame->GetValueObjectForFrameVariable(local_sp, use_dynamic);
4448480093f4SDimitry Andric if (value_sp) {
4449480093f4SDimitry Andric ValueObjectSP synthetic_value_sp = value_sp->GetSyntheticValue();
4450480093f4SDimitry Andric if (synthetic_value_sp)
4451480093f4SDimitry Andric local_values.Append(synthetic_value_sp);
4452480093f4SDimitry Andric else
4453480093f4SDimitry Andric local_values.Append(value_sp);
4454480093f4SDimitry Andric }
4455480093f4SDimitry Andric }
4456480093f4SDimitry Andric // Update the values
4457480093f4SDimitry Andric SetValues(local_values);
4458480093f4SDimitry Andric }
4459480093f4SDimitry Andric }
4460480093f4SDimitry Andric } else {
4461480093f4SDimitry Andric m_frame_block = nullptr;
4462480093f4SDimitry Andric // Update the values with an empty list if there is no frame
4463480093f4SDimitry Andric SetValues(local_values);
4464480093f4SDimitry Andric }
4465480093f4SDimitry Andric
4466480093f4SDimitry Andric return ValueObjectListDelegate::WindowDelegateDraw(window, force);
4467480093f4SDimitry Andric }
4468480093f4SDimitry Andric
4469480093f4SDimitry Andric protected:
4470480093f4SDimitry Andric Debugger &m_debugger;
4471480093f4SDimitry Andric Block *m_frame_block;
4472480093f4SDimitry Andric };
4473480093f4SDimitry Andric
4474480093f4SDimitry Andric class RegistersWindowDelegate : public ValueObjectListDelegate {
4475480093f4SDimitry Andric public:
RegistersWindowDelegate(Debugger & debugger)4476480093f4SDimitry Andric RegistersWindowDelegate(Debugger &debugger)
4477480093f4SDimitry Andric : ValueObjectListDelegate(), m_debugger(debugger) {}
4478480093f4SDimitry Andric
4479480093f4SDimitry Andric ~RegistersWindowDelegate() override = default;
4480480093f4SDimitry Andric
WindowDelegateGetHelpText()4481480093f4SDimitry Andric const char *WindowDelegateGetHelpText() override {
4482480093f4SDimitry Andric return "Register window keyboard shortcuts:";
4483480093f4SDimitry Andric }
4484480093f4SDimitry Andric
WindowDelegateDraw(Window & window,bool force)4485480093f4SDimitry Andric bool WindowDelegateDraw(Window &window, bool force) override {
4486480093f4SDimitry Andric ExecutionContext exe_ctx(
4487480093f4SDimitry Andric m_debugger.GetCommandInterpreter().GetExecutionContext());
4488480093f4SDimitry Andric StackFrame *frame = exe_ctx.GetFramePtr();
4489480093f4SDimitry Andric
4490480093f4SDimitry Andric ValueObjectList value_list;
4491480093f4SDimitry Andric if (frame) {
4492480093f4SDimitry Andric if (frame->GetStackID() != m_stack_id) {
4493480093f4SDimitry Andric m_stack_id = frame->GetStackID();
4494480093f4SDimitry Andric RegisterContextSP reg_ctx(frame->GetRegisterContext());
4495480093f4SDimitry Andric if (reg_ctx) {
4496480093f4SDimitry Andric const uint32_t num_sets = reg_ctx->GetRegisterSetCount();
4497480093f4SDimitry Andric for (uint32_t set_idx = 0; set_idx < num_sets; ++set_idx) {
4498480093f4SDimitry Andric value_list.Append(
4499480093f4SDimitry Andric ValueObjectRegisterSet::Create(frame, reg_ctx, set_idx));
4500480093f4SDimitry Andric }
4501480093f4SDimitry Andric }
4502480093f4SDimitry Andric SetValues(value_list);
4503480093f4SDimitry Andric }
4504480093f4SDimitry Andric } else {
4505480093f4SDimitry Andric Process *process = exe_ctx.GetProcessPtr();
4506480093f4SDimitry Andric if (process && process->IsAlive())
4507480093f4SDimitry Andric return true; // Don't do any updating if we are running
4508480093f4SDimitry Andric else {
4509480093f4SDimitry Andric // Update the values with an empty list if there is no process or the
4510480093f4SDimitry Andric // process isn't alive anymore
4511480093f4SDimitry Andric SetValues(value_list);
4512480093f4SDimitry Andric }
4513480093f4SDimitry Andric }
4514480093f4SDimitry Andric return ValueObjectListDelegate::WindowDelegateDraw(window, force);
4515480093f4SDimitry Andric }
4516480093f4SDimitry Andric
4517480093f4SDimitry Andric protected:
4518480093f4SDimitry Andric Debugger &m_debugger;
4519480093f4SDimitry Andric StackID m_stack_id;
4520480093f4SDimitry Andric };
4521480093f4SDimitry Andric
CursesKeyToCString(int ch)4522480093f4SDimitry Andric static const char *CursesKeyToCString(int ch) {
4523480093f4SDimitry Andric static char g_desc[32];
4524480093f4SDimitry Andric if (ch >= KEY_F0 && ch < KEY_F0 + 64) {
4525480093f4SDimitry Andric snprintf(g_desc, sizeof(g_desc), "F%u", ch - KEY_F0);
4526480093f4SDimitry Andric return g_desc;
4527480093f4SDimitry Andric }
4528480093f4SDimitry Andric switch (ch) {
4529480093f4SDimitry Andric case KEY_DOWN:
4530480093f4SDimitry Andric return "down";
4531480093f4SDimitry Andric case KEY_UP:
4532480093f4SDimitry Andric return "up";
4533480093f4SDimitry Andric case KEY_LEFT:
4534480093f4SDimitry Andric return "left";
4535480093f4SDimitry Andric case KEY_RIGHT:
4536480093f4SDimitry Andric return "right";
4537480093f4SDimitry Andric case KEY_HOME:
4538480093f4SDimitry Andric return "home";
4539480093f4SDimitry Andric case KEY_BACKSPACE:
4540480093f4SDimitry Andric return "backspace";
4541480093f4SDimitry Andric case KEY_DL:
4542480093f4SDimitry Andric return "delete-line";
4543480093f4SDimitry Andric case KEY_IL:
4544480093f4SDimitry Andric return "insert-line";
4545480093f4SDimitry Andric case KEY_DC:
4546480093f4SDimitry Andric return "delete-char";
4547480093f4SDimitry Andric case KEY_IC:
4548480093f4SDimitry Andric return "insert-char";
4549480093f4SDimitry Andric case KEY_CLEAR:
4550480093f4SDimitry Andric return "clear";
4551480093f4SDimitry Andric case KEY_EOS:
4552480093f4SDimitry Andric return "clear-to-eos";
4553480093f4SDimitry Andric case KEY_EOL:
4554480093f4SDimitry Andric return "clear-to-eol";
4555480093f4SDimitry Andric case KEY_SF:
4556480093f4SDimitry Andric return "scroll-forward";
4557480093f4SDimitry Andric case KEY_SR:
4558480093f4SDimitry Andric return "scroll-backward";
4559480093f4SDimitry Andric case KEY_NPAGE:
4560480093f4SDimitry Andric return "page-down";
4561480093f4SDimitry Andric case KEY_PPAGE:
4562480093f4SDimitry Andric return "page-up";
4563480093f4SDimitry Andric case KEY_STAB:
4564480093f4SDimitry Andric return "set-tab";
4565480093f4SDimitry Andric case KEY_CTAB:
4566480093f4SDimitry Andric return "clear-tab";
4567480093f4SDimitry Andric case KEY_CATAB:
4568480093f4SDimitry Andric return "clear-all-tabs";
4569480093f4SDimitry Andric case KEY_ENTER:
4570480093f4SDimitry Andric return "enter";
4571480093f4SDimitry Andric case KEY_PRINT:
4572480093f4SDimitry Andric return "print";
4573480093f4SDimitry Andric case KEY_LL:
4574480093f4SDimitry Andric return "lower-left key";
4575480093f4SDimitry Andric case KEY_A1:
4576480093f4SDimitry Andric return "upper left of keypad";
4577480093f4SDimitry Andric case KEY_A3:
4578480093f4SDimitry Andric return "upper right of keypad";
4579480093f4SDimitry Andric case KEY_B2:
4580480093f4SDimitry Andric return "center of keypad";
4581480093f4SDimitry Andric case KEY_C1:
4582480093f4SDimitry Andric return "lower left of keypad";
4583480093f4SDimitry Andric case KEY_C3:
4584480093f4SDimitry Andric return "lower right of keypad";
4585480093f4SDimitry Andric case KEY_BTAB:
4586480093f4SDimitry Andric return "back-tab key";
4587480093f4SDimitry Andric case KEY_BEG:
4588480093f4SDimitry Andric return "begin key";
4589480093f4SDimitry Andric case KEY_CANCEL:
4590480093f4SDimitry Andric return "cancel key";
4591480093f4SDimitry Andric case KEY_CLOSE:
4592480093f4SDimitry Andric return "close key";
4593480093f4SDimitry Andric case KEY_COMMAND:
4594480093f4SDimitry Andric return "command key";
4595480093f4SDimitry Andric case KEY_COPY:
4596480093f4SDimitry Andric return "copy key";
4597480093f4SDimitry Andric case KEY_CREATE:
4598480093f4SDimitry Andric return "create key";
4599480093f4SDimitry Andric case KEY_END:
4600480093f4SDimitry Andric return "end key";
4601480093f4SDimitry Andric case KEY_EXIT:
4602480093f4SDimitry Andric return "exit key";
4603480093f4SDimitry Andric case KEY_FIND:
4604480093f4SDimitry Andric return "find key";
4605480093f4SDimitry Andric case KEY_HELP:
4606480093f4SDimitry Andric return "help key";
4607480093f4SDimitry Andric case KEY_MARK:
4608480093f4SDimitry Andric return "mark key";
4609480093f4SDimitry Andric case KEY_MESSAGE:
4610480093f4SDimitry Andric return "message key";
4611480093f4SDimitry Andric case KEY_MOVE:
4612480093f4SDimitry Andric return "move key";
4613480093f4SDimitry Andric case KEY_NEXT:
4614480093f4SDimitry Andric return "next key";
4615480093f4SDimitry Andric case KEY_OPEN:
4616480093f4SDimitry Andric return "open key";
4617480093f4SDimitry Andric case KEY_OPTIONS:
4618480093f4SDimitry Andric return "options key";
4619480093f4SDimitry Andric case KEY_PREVIOUS:
4620480093f4SDimitry Andric return "previous key";
4621480093f4SDimitry Andric case KEY_REDO:
4622480093f4SDimitry Andric return "redo key";
4623480093f4SDimitry Andric case KEY_REFERENCE:
4624480093f4SDimitry Andric return "reference key";
4625480093f4SDimitry Andric case KEY_REFRESH:
4626480093f4SDimitry Andric return "refresh key";
4627480093f4SDimitry Andric case KEY_REPLACE:
4628480093f4SDimitry Andric return "replace key";
4629480093f4SDimitry Andric case KEY_RESTART:
4630480093f4SDimitry Andric return "restart key";
4631480093f4SDimitry Andric case KEY_RESUME:
4632480093f4SDimitry Andric return "resume key";
4633480093f4SDimitry Andric case KEY_SAVE:
4634480093f4SDimitry Andric return "save key";
4635480093f4SDimitry Andric case KEY_SBEG:
4636480093f4SDimitry Andric return "shifted begin key";
4637480093f4SDimitry Andric case KEY_SCANCEL:
4638480093f4SDimitry Andric return "shifted cancel key";
4639480093f4SDimitry Andric case KEY_SCOMMAND:
4640480093f4SDimitry Andric return "shifted command key";
4641480093f4SDimitry Andric case KEY_SCOPY:
4642480093f4SDimitry Andric return "shifted copy key";
4643480093f4SDimitry Andric case KEY_SCREATE:
4644480093f4SDimitry Andric return "shifted create key";
4645480093f4SDimitry Andric case KEY_SDC:
4646480093f4SDimitry Andric return "shifted delete-character key";
4647480093f4SDimitry Andric case KEY_SDL:
4648480093f4SDimitry Andric return "shifted delete-line key";
4649480093f4SDimitry Andric case KEY_SELECT:
4650480093f4SDimitry Andric return "select key";
4651480093f4SDimitry Andric case KEY_SEND:
4652480093f4SDimitry Andric return "shifted end key";
4653480093f4SDimitry Andric case KEY_SEOL:
4654480093f4SDimitry Andric return "shifted clear-to-end-of-line key";
4655480093f4SDimitry Andric case KEY_SEXIT:
4656480093f4SDimitry Andric return "shifted exit key";
4657480093f4SDimitry Andric case KEY_SFIND:
4658480093f4SDimitry Andric return "shifted find key";
4659480093f4SDimitry Andric case KEY_SHELP:
4660480093f4SDimitry Andric return "shifted help key";
4661480093f4SDimitry Andric case KEY_SHOME:
4662480093f4SDimitry Andric return "shifted home key";
4663480093f4SDimitry Andric case KEY_SIC:
4664480093f4SDimitry Andric return "shifted insert-character key";
4665480093f4SDimitry Andric case KEY_SLEFT:
4666480093f4SDimitry Andric return "shifted left-arrow key";
4667480093f4SDimitry Andric case KEY_SMESSAGE:
4668480093f4SDimitry Andric return "shifted message key";
4669480093f4SDimitry Andric case KEY_SMOVE:
4670480093f4SDimitry Andric return "shifted move key";
4671480093f4SDimitry Andric case KEY_SNEXT:
4672480093f4SDimitry Andric return "shifted next key";
4673480093f4SDimitry Andric case KEY_SOPTIONS:
4674480093f4SDimitry Andric return "shifted options key";
4675480093f4SDimitry Andric case KEY_SPREVIOUS:
4676480093f4SDimitry Andric return "shifted previous key";
4677480093f4SDimitry Andric case KEY_SPRINT:
4678480093f4SDimitry Andric return "shifted print key";
4679480093f4SDimitry Andric case KEY_SREDO:
4680480093f4SDimitry Andric return "shifted redo key";
4681480093f4SDimitry Andric case KEY_SREPLACE:
4682480093f4SDimitry Andric return "shifted replace key";
4683480093f4SDimitry Andric case KEY_SRIGHT:
4684480093f4SDimitry Andric return "shifted right-arrow key";
4685480093f4SDimitry Andric case KEY_SRSUME:
4686480093f4SDimitry Andric return "shifted resume key";
4687480093f4SDimitry Andric case KEY_SSAVE:
4688480093f4SDimitry Andric return "shifted save key";
4689480093f4SDimitry Andric case KEY_SSUSPEND:
4690480093f4SDimitry Andric return "shifted suspend key";
4691480093f4SDimitry Andric case KEY_SUNDO:
4692480093f4SDimitry Andric return "shifted undo key";
4693480093f4SDimitry Andric case KEY_SUSPEND:
4694480093f4SDimitry Andric return "suspend key";
4695480093f4SDimitry Andric case KEY_UNDO:
4696480093f4SDimitry Andric return "undo key";
4697480093f4SDimitry Andric case KEY_MOUSE:
4698480093f4SDimitry Andric return "Mouse event has occurred";
4699480093f4SDimitry Andric case KEY_RESIZE:
4700480093f4SDimitry Andric return "Terminal resize event";
4701480093f4SDimitry Andric #ifdef KEY_EVENT
4702480093f4SDimitry Andric case KEY_EVENT:
4703480093f4SDimitry Andric return "We were interrupted by an event";
4704480093f4SDimitry Andric #endif
4705480093f4SDimitry Andric case KEY_RETURN:
4706480093f4SDimitry Andric return "return";
4707480093f4SDimitry Andric case ' ':
4708480093f4SDimitry Andric return "space";
4709480093f4SDimitry Andric case '\t':
4710480093f4SDimitry Andric return "tab";
4711480093f4SDimitry Andric case KEY_ESCAPE:
4712480093f4SDimitry Andric return "escape";
4713480093f4SDimitry Andric default:
47145ffd83dbSDimitry Andric if (llvm::isPrint(ch))
4715480093f4SDimitry Andric snprintf(g_desc, sizeof(g_desc), "%c", ch);
4716480093f4SDimitry Andric else
4717480093f4SDimitry Andric snprintf(g_desc, sizeof(g_desc), "\\x%2.2x", ch);
4718480093f4SDimitry Andric return g_desc;
4719480093f4SDimitry Andric }
4720480093f4SDimitry Andric return nullptr;
4721480093f4SDimitry Andric }
4722480093f4SDimitry Andric
HelpDialogDelegate(const char * text,KeyHelp * key_help_array)4723480093f4SDimitry Andric HelpDialogDelegate::HelpDialogDelegate(const char *text,
4724480093f4SDimitry Andric KeyHelp *key_help_array)
4725480093f4SDimitry Andric : m_text(), m_first_visible_line(0) {
4726480093f4SDimitry Andric if (text && text[0]) {
4727480093f4SDimitry Andric m_text.SplitIntoLines(text);
4728480093f4SDimitry Andric m_text.AppendString("");
4729480093f4SDimitry Andric }
4730480093f4SDimitry Andric if (key_help_array) {
4731480093f4SDimitry Andric for (KeyHelp *key = key_help_array; key->ch; ++key) {
4732480093f4SDimitry Andric StreamString key_description;
4733480093f4SDimitry Andric key_description.Printf("%10s - %s", CursesKeyToCString(key->ch),
4734480093f4SDimitry Andric key->description);
4735480093f4SDimitry Andric m_text.AppendString(key_description.GetString());
4736480093f4SDimitry Andric }
4737480093f4SDimitry Andric }
4738480093f4SDimitry Andric }
4739480093f4SDimitry Andric
4740480093f4SDimitry Andric HelpDialogDelegate::~HelpDialogDelegate() = default;
4741480093f4SDimitry Andric
WindowDelegateDraw(Window & window,bool force)4742480093f4SDimitry Andric bool HelpDialogDelegate::WindowDelegateDraw(Window &window, bool force) {
4743480093f4SDimitry Andric window.Erase();
4744480093f4SDimitry Andric const int window_height = window.GetHeight();
4745480093f4SDimitry Andric int x = 2;
4746480093f4SDimitry Andric int y = 1;
4747480093f4SDimitry Andric const int min_y = y;
4748480093f4SDimitry Andric const int max_y = window_height - 1 - y;
4749480093f4SDimitry Andric const size_t num_visible_lines = max_y - min_y + 1;
4750480093f4SDimitry Andric const size_t num_lines = m_text.GetSize();
4751480093f4SDimitry Andric const char *bottom_message;
4752480093f4SDimitry Andric if (num_lines <= num_visible_lines)
4753480093f4SDimitry Andric bottom_message = "Press any key to exit";
4754480093f4SDimitry Andric else
4755480093f4SDimitry Andric bottom_message = "Use arrows to scroll, any other key to exit";
4756480093f4SDimitry Andric window.DrawTitleBox(window.GetName(), bottom_message);
4757480093f4SDimitry Andric while (y <= max_y) {
4758480093f4SDimitry Andric window.MoveCursor(x, y);
4759480093f4SDimitry Andric window.PutCStringTruncated(
4760af732203SDimitry Andric 1, m_text.GetStringAtIndex(m_first_visible_line + y - min_y));
4761480093f4SDimitry Andric ++y;
4762480093f4SDimitry Andric }
4763480093f4SDimitry Andric return true;
4764480093f4SDimitry Andric }
4765480093f4SDimitry Andric
WindowDelegateHandleChar(Window & window,int key)4766480093f4SDimitry Andric HandleCharResult HelpDialogDelegate::WindowDelegateHandleChar(Window &window,
4767480093f4SDimitry Andric int key) {
4768480093f4SDimitry Andric bool done = false;
4769480093f4SDimitry Andric const size_t num_lines = m_text.GetSize();
4770480093f4SDimitry Andric const size_t num_visible_lines = window.GetHeight() - 2;
4771480093f4SDimitry Andric
4772480093f4SDimitry Andric if (num_lines <= num_visible_lines) {
4773480093f4SDimitry Andric done = true;
4774480093f4SDimitry Andric // If we have all lines visible and don't need scrolling, then any key
4775480093f4SDimitry Andric // press will cause us to exit
4776480093f4SDimitry Andric } else {
4777480093f4SDimitry Andric switch (key) {
4778480093f4SDimitry Andric case KEY_UP:
4779480093f4SDimitry Andric if (m_first_visible_line > 0)
4780480093f4SDimitry Andric --m_first_visible_line;
4781480093f4SDimitry Andric break;
4782480093f4SDimitry Andric
4783480093f4SDimitry Andric case KEY_DOWN:
4784480093f4SDimitry Andric if (m_first_visible_line + num_visible_lines < num_lines)
4785480093f4SDimitry Andric ++m_first_visible_line;
4786480093f4SDimitry Andric break;
4787480093f4SDimitry Andric
4788480093f4SDimitry Andric case KEY_PPAGE:
4789480093f4SDimitry Andric case ',':
4790480093f4SDimitry Andric if (m_first_visible_line > 0) {
4791480093f4SDimitry Andric if (static_cast<size_t>(m_first_visible_line) >= num_visible_lines)
4792480093f4SDimitry Andric m_first_visible_line -= num_visible_lines;
4793480093f4SDimitry Andric else
4794480093f4SDimitry Andric m_first_visible_line = 0;
4795480093f4SDimitry Andric }
4796480093f4SDimitry Andric break;
4797480093f4SDimitry Andric
4798480093f4SDimitry Andric case KEY_NPAGE:
4799480093f4SDimitry Andric case '.':
4800480093f4SDimitry Andric if (m_first_visible_line + num_visible_lines < num_lines) {
4801480093f4SDimitry Andric m_first_visible_line += num_visible_lines;
4802480093f4SDimitry Andric if (static_cast<size_t>(m_first_visible_line) > num_lines)
4803480093f4SDimitry Andric m_first_visible_line = num_lines - num_visible_lines;
4804480093f4SDimitry Andric }
4805480093f4SDimitry Andric break;
4806480093f4SDimitry Andric
4807480093f4SDimitry Andric default:
4808480093f4SDimitry Andric done = true;
4809480093f4SDimitry Andric break;
4810480093f4SDimitry Andric }
4811480093f4SDimitry Andric }
4812480093f4SDimitry Andric if (done)
4813480093f4SDimitry Andric window.GetParent()->RemoveSubWindow(&window);
4814480093f4SDimitry Andric return eKeyHandled;
4815480093f4SDimitry Andric }
4816480093f4SDimitry Andric
4817480093f4SDimitry Andric class ApplicationDelegate : public WindowDelegate, public MenuDelegate {
4818480093f4SDimitry Andric public:
4819480093f4SDimitry Andric enum {
4820480093f4SDimitry Andric eMenuID_LLDB = 1,
4821480093f4SDimitry Andric eMenuID_LLDBAbout,
4822480093f4SDimitry Andric eMenuID_LLDBExit,
4823480093f4SDimitry Andric
4824480093f4SDimitry Andric eMenuID_Target,
4825480093f4SDimitry Andric eMenuID_TargetCreate,
4826480093f4SDimitry Andric eMenuID_TargetDelete,
4827480093f4SDimitry Andric
4828480093f4SDimitry Andric eMenuID_Process,
4829480093f4SDimitry Andric eMenuID_ProcessAttach,
4830af732203SDimitry Andric eMenuID_ProcessDetachResume,
4831af732203SDimitry Andric eMenuID_ProcessDetachSuspended,
4832480093f4SDimitry Andric eMenuID_ProcessLaunch,
4833480093f4SDimitry Andric eMenuID_ProcessContinue,
4834480093f4SDimitry Andric eMenuID_ProcessHalt,
4835480093f4SDimitry Andric eMenuID_ProcessKill,
4836480093f4SDimitry Andric
4837480093f4SDimitry Andric eMenuID_Thread,
4838480093f4SDimitry Andric eMenuID_ThreadStepIn,
4839480093f4SDimitry Andric eMenuID_ThreadStepOver,
4840480093f4SDimitry Andric eMenuID_ThreadStepOut,
4841480093f4SDimitry Andric
4842480093f4SDimitry Andric eMenuID_View,
4843480093f4SDimitry Andric eMenuID_ViewBacktrace,
4844480093f4SDimitry Andric eMenuID_ViewRegisters,
4845480093f4SDimitry Andric eMenuID_ViewSource,
4846480093f4SDimitry Andric eMenuID_ViewVariables,
4847480093f4SDimitry Andric
4848480093f4SDimitry Andric eMenuID_Help,
4849480093f4SDimitry Andric eMenuID_HelpGUIHelp
4850480093f4SDimitry Andric };
4851480093f4SDimitry Andric
ApplicationDelegate(Application & app,Debugger & debugger)4852480093f4SDimitry Andric ApplicationDelegate(Application &app, Debugger &debugger)
4853480093f4SDimitry Andric : WindowDelegate(), MenuDelegate(), m_app(app), m_debugger(debugger) {}
4854480093f4SDimitry Andric
4855480093f4SDimitry Andric ~ApplicationDelegate() override = default;
4856480093f4SDimitry Andric
WindowDelegateDraw(Window & window,bool force)4857480093f4SDimitry Andric bool WindowDelegateDraw(Window &window, bool force) override {
4858480093f4SDimitry Andric return false; // Drawing not handled, let standard window drawing happen
4859480093f4SDimitry Andric }
4860480093f4SDimitry Andric
WindowDelegateHandleChar(Window & window,int key)4861480093f4SDimitry Andric HandleCharResult WindowDelegateHandleChar(Window &window, int key) override {
4862480093f4SDimitry Andric switch (key) {
4863480093f4SDimitry Andric case '\t':
4864480093f4SDimitry Andric window.SelectNextWindowAsActive();
4865480093f4SDimitry Andric return eKeyHandled;
4866480093f4SDimitry Andric
4867*5f7ddb14SDimitry Andric case KEY_SHIFT_TAB:
4868af732203SDimitry Andric window.SelectPreviousWindowAsActive();
4869af732203SDimitry Andric return eKeyHandled;
4870af732203SDimitry Andric
4871480093f4SDimitry Andric case 'h':
4872480093f4SDimitry Andric window.CreateHelpSubwindow();
4873480093f4SDimitry Andric return eKeyHandled;
4874480093f4SDimitry Andric
4875480093f4SDimitry Andric case KEY_ESCAPE:
4876480093f4SDimitry Andric return eQuitApplication;
4877480093f4SDimitry Andric
4878480093f4SDimitry Andric default:
4879480093f4SDimitry Andric break;
4880480093f4SDimitry Andric }
4881480093f4SDimitry Andric return eKeyNotHandled;
4882480093f4SDimitry Andric }
4883480093f4SDimitry Andric
WindowDelegateGetHelpText()4884480093f4SDimitry Andric const char *WindowDelegateGetHelpText() override {
4885480093f4SDimitry Andric return "Welcome to the LLDB curses GUI.\n\n"
4886480093f4SDimitry Andric "Press the TAB key to change the selected view.\n"
4887480093f4SDimitry Andric "Each view has its own keyboard shortcuts, press 'h' to open a "
4888480093f4SDimitry Andric "dialog to display them.\n\n"
4889480093f4SDimitry Andric "Common key bindings for all views:";
4890480093f4SDimitry Andric }
4891480093f4SDimitry Andric
WindowDelegateGetKeyHelp()4892480093f4SDimitry Andric KeyHelp *WindowDelegateGetKeyHelp() override {
4893480093f4SDimitry Andric static curses::KeyHelp g_source_view_key_help[] = {
4894480093f4SDimitry Andric {'\t', "Select next view"},
4895af732203SDimitry Andric {KEY_BTAB, "Select previous view"},
4896480093f4SDimitry Andric {'h', "Show help dialog with view specific key bindings"},
4897480093f4SDimitry Andric {',', "Page up"},
4898480093f4SDimitry Andric {'.', "Page down"},
4899480093f4SDimitry Andric {KEY_UP, "Select previous"},
4900480093f4SDimitry Andric {KEY_DOWN, "Select next"},
4901480093f4SDimitry Andric {KEY_LEFT, "Unexpand or select parent"},
4902480093f4SDimitry Andric {KEY_RIGHT, "Expand"},
4903480093f4SDimitry Andric {KEY_PPAGE, "Page up"},
4904480093f4SDimitry Andric {KEY_NPAGE, "Page down"},
4905480093f4SDimitry Andric {'\0', nullptr}};
4906480093f4SDimitry Andric return g_source_view_key_help;
4907480093f4SDimitry Andric }
4908480093f4SDimitry Andric
MenuDelegateAction(Menu & menu)4909480093f4SDimitry Andric MenuActionResult MenuDelegateAction(Menu &menu) override {
4910480093f4SDimitry Andric switch (menu.GetIdentifier()) {
4911480093f4SDimitry Andric case eMenuID_ThreadStepIn: {
4912480093f4SDimitry Andric ExecutionContext exe_ctx =
4913480093f4SDimitry Andric m_debugger.GetCommandInterpreter().GetExecutionContext();
4914480093f4SDimitry Andric if (exe_ctx.HasThreadScope()) {
4915480093f4SDimitry Andric Process *process = exe_ctx.GetProcessPtr();
4916480093f4SDimitry Andric if (process && process->IsAlive() &&
4917480093f4SDimitry Andric StateIsStoppedState(process->GetState(), true))
4918480093f4SDimitry Andric exe_ctx.GetThreadRef().StepIn(true);
4919480093f4SDimitry Andric }
4920480093f4SDimitry Andric }
4921480093f4SDimitry Andric return MenuActionResult::Handled;
4922480093f4SDimitry Andric
4923480093f4SDimitry Andric case eMenuID_ThreadStepOut: {
4924480093f4SDimitry Andric ExecutionContext exe_ctx =
4925480093f4SDimitry Andric m_debugger.GetCommandInterpreter().GetExecutionContext();
4926480093f4SDimitry Andric if (exe_ctx.HasThreadScope()) {
4927480093f4SDimitry Andric Process *process = exe_ctx.GetProcessPtr();
4928480093f4SDimitry Andric if (process && process->IsAlive() &&
4929480093f4SDimitry Andric StateIsStoppedState(process->GetState(), true))
4930480093f4SDimitry Andric exe_ctx.GetThreadRef().StepOut();
4931480093f4SDimitry Andric }
4932480093f4SDimitry Andric }
4933480093f4SDimitry Andric return MenuActionResult::Handled;
4934480093f4SDimitry Andric
4935480093f4SDimitry Andric case eMenuID_ThreadStepOver: {
4936480093f4SDimitry Andric ExecutionContext exe_ctx =
4937480093f4SDimitry Andric m_debugger.GetCommandInterpreter().GetExecutionContext();
4938480093f4SDimitry Andric if (exe_ctx.HasThreadScope()) {
4939480093f4SDimitry Andric Process *process = exe_ctx.GetProcessPtr();
4940480093f4SDimitry Andric if (process && process->IsAlive() &&
4941480093f4SDimitry Andric StateIsStoppedState(process->GetState(), true))
4942480093f4SDimitry Andric exe_ctx.GetThreadRef().StepOver(true);
4943480093f4SDimitry Andric }
4944480093f4SDimitry Andric }
4945480093f4SDimitry Andric return MenuActionResult::Handled;
4946480093f4SDimitry Andric
4947*5f7ddb14SDimitry Andric case eMenuID_ProcessAttach: {
4948*5f7ddb14SDimitry Andric WindowSP main_window_sp = m_app.GetMainWindow();
4949*5f7ddb14SDimitry Andric FormDelegateSP form_delegate_sp = FormDelegateSP(
4950*5f7ddb14SDimitry Andric new ProcessAttachFormDelegate(m_debugger, main_window_sp));
4951*5f7ddb14SDimitry Andric Rect bounds = main_window_sp->GetCenteredRect(80, 22);
4952*5f7ddb14SDimitry Andric WindowSP form_window_sp = main_window_sp->CreateSubWindow(
4953*5f7ddb14SDimitry Andric form_delegate_sp->GetName().c_str(), bounds, true);
4954*5f7ddb14SDimitry Andric WindowDelegateSP window_delegate_sp =
4955*5f7ddb14SDimitry Andric WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));
4956*5f7ddb14SDimitry Andric form_window_sp->SetDelegate(window_delegate_sp);
4957*5f7ddb14SDimitry Andric return MenuActionResult::Handled;
4958*5f7ddb14SDimitry Andric }
4959*5f7ddb14SDimitry Andric
4960480093f4SDimitry Andric case eMenuID_ProcessContinue: {
4961480093f4SDimitry Andric ExecutionContext exe_ctx =
4962480093f4SDimitry Andric m_debugger.GetCommandInterpreter().GetExecutionContext();
4963480093f4SDimitry Andric if (exe_ctx.HasProcessScope()) {
4964480093f4SDimitry Andric Process *process = exe_ctx.GetProcessPtr();
4965480093f4SDimitry Andric if (process && process->IsAlive() &&
4966480093f4SDimitry Andric StateIsStoppedState(process->GetState(), true))
4967480093f4SDimitry Andric process->Resume();
4968480093f4SDimitry Andric }
4969480093f4SDimitry Andric }
4970480093f4SDimitry Andric return MenuActionResult::Handled;
4971480093f4SDimitry Andric
4972480093f4SDimitry Andric case eMenuID_ProcessKill: {
4973480093f4SDimitry Andric ExecutionContext exe_ctx =
4974480093f4SDimitry Andric m_debugger.GetCommandInterpreter().GetExecutionContext();
4975480093f4SDimitry Andric if (exe_ctx.HasProcessScope()) {
4976480093f4SDimitry Andric Process *process = exe_ctx.GetProcessPtr();
4977480093f4SDimitry Andric if (process && process->IsAlive())
4978480093f4SDimitry Andric process->Destroy(false);
4979480093f4SDimitry Andric }
4980480093f4SDimitry Andric }
4981480093f4SDimitry Andric return MenuActionResult::Handled;
4982480093f4SDimitry Andric
4983480093f4SDimitry Andric case eMenuID_ProcessHalt: {
4984480093f4SDimitry Andric ExecutionContext exe_ctx =
4985480093f4SDimitry Andric m_debugger.GetCommandInterpreter().GetExecutionContext();
4986480093f4SDimitry Andric if (exe_ctx.HasProcessScope()) {
4987480093f4SDimitry Andric Process *process = exe_ctx.GetProcessPtr();
4988480093f4SDimitry Andric if (process && process->IsAlive())
4989480093f4SDimitry Andric process->Halt();
4990480093f4SDimitry Andric }
4991480093f4SDimitry Andric }
4992480093f4SDimitry Andric return MenuActionResult::Handled;
4993480093f4SDimitry Andric
4994af732203SDimitry Andric case eMenuID_ProcessDetachResume:
4995af732203SDimitry Andric case eMenuID_ProcessDetachSuspended: {
4996480093f4SDimitry Andric ExecutionContext exe_ctx =
4997480093f4SDimitry Andric m_debugger.GetCommandInterpreter().GetExecutionContext();
4998480093f4SDimitry Andric if (exe_ctx.HasProcessScope()) {
4999480093f4SDimitry Andric Process *process = exe_ctx.GetProcessPtr();
5000480093f4SDimitry Andric if (process && process->IsAlive())
5001af732203SDimitry Andric process->Detach(menu.GetIdentifier() ==
5002af732203SDimitry Andric eMenuID_ProcessDetachSuspended);
5003480093f4SDimitry Andric }
5004480093f4SDimitry Andric }
5005480093f4SDimitry Andric return MenuActionResult::Handled;
5006480093f4SDimitry Andric
5007480093f4SDimitry Andric case eMenuID_Process: {
5008480093f4SDimitry Andric // Populate the menu with all of the threads if the process is stopped
5009480093f4SDimitry Andric // when the Process menu gets selected and is about to display its
5010480093f4SDimitry Andric // submenu.
5011480093f4SDimitry Andric Menus &submenus = menu.GetSubmenus();
5012480093f4SDimitry Andric ExecutionContext exe_ctx =
5013480093f4SDimitry Andric m_debugger.GetCommandInterpreter().GetExecutionContext();
5014480093f4SDimitry Andric Process *process = exe_ctx.GetProcessPtr();
5015480093f4SDimitry Andric if (process && process->IsAlive() &&
5016480093f4SDimitry Andric StateIsStoppedState(process->GetState(), true)) {
5017480093f4SDimitry Andric if (submenus.size() == 7)
5018480093f4SDimitry Andric menu.AddSubmenu(MenuSP(new Menu(Menu::Type::Separator)));
5019480093f4SDimitry Andric else if (submenus.size() > 8)
5020480093f4SDimitry Andric submenus.erase(submenus.begin() + 8, submenus.end());
5021480093f4SDimitry Andric
5022480093f4SDimitry Andric ThreadList &threads = process->GetThreadList();
5023480093f4SDimitry Andric std::lock_guard<std::recursive_mutex> guard(threads.GetMutex());
5024480093f4SDimitry Andric size_t num_threads = threads.GetSize();
5025480093f4SDimitry Andric for (size_t i = 0; i < num_threads; ++i) {
5026480093f4SDimitry Andric ThreadSP thread_sp = threads.GetThreadAtIndex(i);
5027480093f4SDimitry Andric char menu_char = '\0';
5028480093f4SDimitry Andric if (i < 9)
5029480093f4SDimitry Andric menu_char = '1' + i;
5030480093f4SDimitry Andric StreamString thread_menu_title;
5031480093f4SDimitry Andric thread_menu_title.Printf("Thread %u", thread_sp->GetIndexID());
5032480093f4SDimitry Andric const char *thread_name = thread_sp->GetName();
5033480093f4SDimitry Andric if (thread_name && thread_name[0])
5034480093f4SDimitry Andric thread_menu_title.Printf(" %s", thread_name);
5035480093f4SDimitry Andric else {
5036480093f4SDimitry Andric const char *queue_name = thread_sp->GetQueueName();
5037480093f4SDimitry Andric if (queue_name && queue_name[0])
5038480093f4SDimitry Andric thread_menu_title.Printf(" %s", queue_name);
5039480093f4SDimitry Andric }
5040480093f4SDimitry Andric menu.AddSubmenu(
5041480093f4SDimitry Andric MenuSP(new Menu(thread_menu_title.GetString().str().c_str(),
5042480093f4SDimitry Andric nullptr, menu_char, thread_sp->GetID())));
5043480093f4SDimitry Andric }
5044480093f4SDimitry Andric } else if (submenus.size() > 7) {
5045480093f4SDimitry Andric // Remove the separator and any other thread submenu items that were
5046480093f4SDimitry Andric // previously added
5047480093f4SDimitry Andric submenus.erase(submenus.begin() + 7, submenus.end());
5048480093f4SDimitry Andric }
5049480093f4SDimitry Andric // Since we are adding and removing items we need to recalculate the name
5050480093f4SDimitry Andric // lengths
5051480093f4SDimitry Andric menu.RecalculateNameLengths();
5052480093f4SDimitry Andric }
5053480093f4SDimitry Andric return MenuActionResult::Handled;
5054480093f4SDimitry Andric
5055480093f4SDimitry Andric case eMenuID_ViewVariables: {
5056480093f4SDimitry Andric WindowSP main_window_sp = m_app.GetMainWindow();
5057480093f4SDimitry Andric WindowSP source_window_sp = main_window_sp->FindSubWindow("Source");
5058480093f4SDimitry Andric WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables");
5059480093f4SDimitry Andric WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers");
5060480093f4SDimitry Andric const Rect source_bounds = source_window_sp->GetBounds();
5061480093f4SDimitry Andric
5062480093f4SDimitry Andric if (variables_window_sp) {
5063480093f4SDimitry Andric const Rect variables_bounds = variables_window_sp->GetBounds();
5064480093f4SDimitry Andric
5065480093f4SDimitry Andric main_window_sp->RemoveSubWindow(variables_window_sp.get());
5066480093f4SDimitry Andric
5067480093f4SDimitry Andric if (registers_window_sp) {
5068480093f4SDimitry Andric // We have a registers window, so give all the area back to the
5069480093f4SDimitry Andric // registers window
5070480093f4SDimitry Andric Rect registers_bounds = variables_bounds;
5071480093f4SDimitry Andric registers_bounds.size.width = source_bounds.size.width;
5072480093f4SDimitry Andric registers_window_sp->SetBounds(registers_bounds);
5073480093f4SDimitry Andric } else {
5074480093f4SDimitry Andric // We have no registers window showing so give the bottom area back
5075480093f4SDimitry Andric // to the source view
5076480093f4SDimitry Andric source_window_sp->Resize(source_bounds.size.width,
5077480093f4SDimitry Andric source_bounds.size.height +
5078480093f4SDimitry Andric variables_bounds.size.height);
5079480093f4SDimitry Andric }
5080480093f4SDimitry Andric } else {
5081480093f4SDimitry Andric Rect new_variables_rect;
5082480093f4SDimitry Andric if (registers_window_sp) {
5083480093f4SDimitry Andric // We have a registers window so split the area of the registers
5084480093f4SDimitry Andric // window into two columns where the left hand side will be the
5085480093f4SDimitry Andric // variables and the right hand side will be the registers
5086480093f4SDimitry Andric const Rect variables_bounds = registers_window_sp->GetBounds();
5087480093f4SDimitry Andric Rect new_registers_rect;
5088480093f4SDimitry Andric variables_bounds.VerticalSplitPercentage(0.50, new_variables_rect,
5089480093f4SDimitry Andric new_registers_rect);
5090480093f4SDimitry Andric registers_window_sp->SetBounds(new_registers_rect);
5091480093f4SDimitry Andric } else {
5092af732203SDimitry Andric // No registers window, grab the bottom part of the source window
5093480093f4SDimitry Andric Rect new_source_rect;
5094480093f4SDimitry Andric source_bounds.HorizontalSplitPercentage(0.70, new_source_rect,
5095480093f4SDimitry Andric new_variables_rect);
5096480093f4SDimitry Andric source_window_sp->SetBounds(new_source_rect);
5097480093f4SDimitry Andric }
5098480093f4SDimitry Andric WindowSP new_window_sp = main_window_sp->CreateSubWindow(
5099480093f4SDimitry Andric "Variables", new_variables_rect, false);
5100480093f4SDimitry Andric new_window_sp->SetDelegate(
5101480093f4SDimitry Andric WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger)));
5102480093f4SDimitry Andric }
5103480093f4SDimitry Andric touchwin(stdscr);
5104480093f4SDimitry Andric }
5105480093f4SDimitry Andric return MenuActionResult::Handled;
5106480093f4SDimitry Andric
5107480093f4SDimitry Andric case eMenuID_ViewRegisters: {
5108480093f4SDimitry Andric WindowSP main_window_sp = m_app.GetMainWindow();
5109480093f4SDimitry Andric WindowSP source_window_sp = main_window_sp->FindSubWindow("Source");
5110480093f4SDimitry Andric WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables");
5111480093f4SDimitry Andric WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers");
5112480093f4SDimitry Andric const Rect source_bounds = source_window_sp->GetBounds();
5113480093f4SDimitry Andric
5114480093f4SDimitry Andric if (registers_window_sp) {
5115480093f4SDimitry Andric if (variables_window_sp) {
5116480093f4SDimitry Andric const Rect variables_bounds = variables_window_sp->GetBounds();
5117480093f4SDimitry Andric
5118480093f4SDimitry Andric // We have a variables window, so give all the area back to the
5119480093f4SDimitry Andric // variables window
5120480093f4SDimitry Andric variables_window_sp->Resize(variables_bounds.size.width +
5121480093f4SDimitry Andric registers_window_sp->GetWidth(),
5122480093f4SDimitry Andric variables_bounds.size.height);
5123480093f4SDimitry Andric } else {
5124480093f4SDimitry Andric // We have no variables window showing so give the bottom area back
5125480093f4SDimitry Andric // to the source view
5126480093f4SDimitry Andric source_window_sp->Resize(source_bounds.size.width,
5127480093f4SDimitry Andric source_bounds.size.height +
5128480093f4SDimitry Andric registers_window_sp->GetHeight());
5129480093f4SDimitry Andric }
5130480093f4SDimitry Andric main_window_sp->RemoveSubWindow(registers_window_sp.get());
5131480093f4SDimitry Andric } else {
5132480093f4SDimitry Andric Rect new_regs_rect;
5133480093f4SDimitry Andric if (variables_window_sp) {
5134480093f4SDimitry Andric // We have a variables window, split it into two columns where the
5135480093f4SDimitry Andric // left hand side will be the variables and the right hand side will
5136480093f4SDimitry Andric // be the registers
5137480093f4SDimitry Andric const Rect variables_bounds = variables_window_sp->GetBounds();
5138480093f4SDimitry Andric Rect new_vars_rect;
5139480093f4SDimitry Andric variables_bounds.VerticalSplitPercentage(0.50, new_vars_rect,
5140480093f4SDimitry Andric new_regs_rect);
5141480093f4SDimitry Andric variables_window_sp->SetBounds(new_vars_rect);
5142480093f4SDimitry Andric } else {
5143af732203SDimitry Andric // No variables window, grab the bottom part of the source window
5144480093f4SDimitry Andric Rect new_source_rect;
5145480093f4SDimitry Andric source_bounds.HorizontalSplitPercentage(0.70, new_source_rect,
5146480093f4SDimitry Andric new_regs_rect);
5147480093f4SDimitry Andric source_window_sp->SetBounds(new_source_rect);
5148480093f4SDimitry Andric }
5149480093f4SDimitry Andric WindowSP new_window_sp =
5150480093f4SDimitry Andric main_window_sp->CreateSubWindow("Registers", new_regs_rect, false);
5151480093f4SDimitry Andric new_window_sp->SetDelegate(
5152480093f4SDimitry Andric WindowDelegateSP(new RegistersWindowDelegate(m_debugger)));
5153480093f4SDimitry Andric }
5154480093f4SDimitry Andric touchwin(stdscr);
5155480093f4SDimitry Andric }
5156480093f4SDimitry Andric return MenuActionResult::Handled;
5157480093f4SDimitry Andric
5158480093f4SDimitry Andric case eMenuID_HelpGUIHelp:
5159480093f4SDimitry Andric m_app.GetMainWindow()->CreateHelpSubwindow();
5160480093f4SDimitry Andric return MenuActionResult::Handled;
5161480093f4SDimitry Andric
5162480093f4SDimitry Andric default:
5163480093f4SDimitry Andric break;
5164480093f4SDimitry Andric }
5165480093f4SDimitry Andric
5166480093f4SDimitry Andric return MenuActionResult::NotHandled;
5167480093f4SDimitry Andric }
5168480093f4SDimitry Andric
5169480093f4SDimitry Andric protected:
5170480093f4SDimitry Andric Application &m_app;
5171480093f4SDimitry Andric Debugger &m_debugger;
5172480093f4SDimitry Andric };
5173480093f4SDimitry Andric
5174480093f4SDimitry Andric class StatusBarWindowDelegate : public WindowDelegate {
5175480093f4SDimitry Andric public:
StatusBarWindowDelegate(Debugger & debugger)5176480093f4SDimitry Andric StatusBarWindowDelegate(Debugger &debugger) : m_debugger(debugger) {
5177480093f4SDimitry Andric FormatEntity::Parse("Thread: ${thread.id%tid}", m_format);
5178480093f4SDimitry Andric }
5179480093f4SDimitry Andric
5180480093f4SDimitry Andric ~StatusBarWindowDelegate() override = default;
5181480093f4SDimitry Andric
WindowDelegateDraw(Window & window,bool force)5182480093f4SDimitry Andric bool WindowDelegateDraw(Window &window, bool force) override {
5183480093f4SDimitry Andric ExecutionContext exe_ctx =
5184480093f4SDimitry Andric m_debugger.GetCommandInterpreter().GetExecutionContext();
5185480093f4SDimitry Andric Process *process = exe_ctx.GetProcessPtr();
5186480093f4SDimitry Andric Thread *thread = exe_ctx.GetThreadPtr();
5187480093f4SDimitry Andric StackFrame *frame = exe_ctx.GetFramePtr();
5188480093f4SDimitry Andric window.Erase();
5189af732203SDimitry Andric window.SetBackground(BlackOnWhite);
5190480093f4SDimitry Andric window.MoveCursor(0, 0);
5191480093f4SDimitry Andric if (process) {
5192480093f4SDimitry Andric const StateType state = process->GetState();
5193480093f4SDimitry Andric window.Printf("Process: %5" PRIu64 " %10s", process->GetID(),
5194480093f4SDimitry Andric StateAsCString(state));
5195480093f4SDimitry Andric
5196480093f4SDimitry Andric if (StateIsStoppedState(state, true)) {
5197480093f4SDimitry Andric StreamString strm;
5198480093f4SDimitry Andric if (thread && FormatEntity::Format(m_format, strm, nullptr, &exe_ctx,
5199480093f4SDimitry Andric nullptr, nullptr, false, false)) {
5200480093f4SDimitry Andric window.MoveCursor(40, 0);
5201af732203SDimitry Andric window.PutCStringTruncated(1, strm.GetString().str().c_str());
5202480093f4SDimitry Andric }
5203480093f4SDimitry Andric
5204480093f4SDimitry Andric window.MoveCursor(60, 0);
5205480093f4SDimitry Andric if (frame)
5206480093f4SDimitry Andric window.Printf("Frame: %3u PC = 0x%16.16" PRIx64,
5207480093f4SDimitry Andric frame->GetFrameIndex(),
5208480093f4SDimitry Andric frame->GetFrameCodeAddress().GetOpcodeLoadAddress(
5209480093f4SDimitry Andric exe_ctx.GetTargetPtr()));
5210480093f4SDimitry Andric } else if (state == eStateExited) {
5211480093f4SDimitry Andric const char *exit_desc = process->GetExitDescription();
5212480093f4SDimitry Andric const int exit_status = process->GetExitStatus();
5213480093f4SDimitry Andric if (exit_desc && exit_desc[0])
5214480093f4SDimitry Andric window.Printf(" with status = %i (%s)", exit_status, exit_desc);
5215480093f4SDimitry Andric else
5216480093f4SDimitry Andric window.Printf(" with status = %i", exit_status);
5217480093f4SDimitry Andric }
5218480093f4SDimitry Andric }
5219480093f4SDimitry Andric return true;
5220480093f4SDimitry Andric }
5221480093f4SDimitry Andric
5222480093f4SDimitry Andric protected:
5223480093f4SDimitry Andric Debugger &m_debugger;
5224480093f4SDimitry Andric FormatEntity::Entry m_format;
5225480093f4SDimitry Andric };
5226480093f4SDimitry Andric
5227480093f4SDimitry Andric class SourceFileWindowDelegate : public WindowDelegate {
5228480093f4SDimitry Andric public:
SourceFileWindowDelegate(Debugger & debugger)5229480093f4SDimitry Andric SourceFileWindowDelegate(Debugger &debugger)
5230480093f4SDimitry Andric : WindowDelegate(), m_debugger(debugger), m_sc(), m_file_sp(),
5231480093f4SDimitry Andric m_disassembly_scope(nullptr), m_disassembly_sp(), m_disassembly_range(),
5232*5f7ddb14SDimitry Andric m_title(), m_tid(LLDB_INVALID_THREAD_ID), m_line_width(4),
5233*5f7ddb14SDimitry Andric m_selected_line(0), m_pc_line(0), m_stop_id(0), m_frame_idx(UINT32_MAX),
5234*5f7ddb14SDimitry Andric m_first_visible_line(0), m_first_visible_column(0), m_min_x(0),
5235*5f7ddb14SDimitry Andric m_min_y(0), m_max_x(0), m_max_y(0) {}
5236480093f4SDimitry Andric
5237480093f4SDimitry Andric ~SourceFileWindowDelegate() override = default;
5238480093f4SDimitry Andric
Update(const SymbolContext & sc)5239480093f4SDimitry Andric void Update(const SymbolContext &sc) { m_sc = sc; }
5240480093f4SDimitry Andric
NumVisibleLines() const5241480093f4SDimitry Andric uint32_t NumVisibleLines() const { return m_max_y - m_min_y; }
5242480093f4SDimitry Andric
WindowDelegateGetHelpText()5243480093f4SDimitry Andric const char *WindowDelegateGetHelpText() override {
5244480093f4SDimitry Andric return "Source/Disassembly window keyboard shortcuts:";
5245480093f4SDimitry Andric }
5246480093f4SDimitry Andric
WindowDelegateGetKeyHelp()5247480093f4SDimitry Andric KeyHelp *WindowDelegateGetKeyHelp() override {
5248480093f4SDimitry Andric static curses::KeyHelp g_source_view_key_help[] = {
5249480093f4SDimitry Andric {KEY_RETURN, "Run to selected line with one shot breakpoint"},
5250480093f4SDimitry Andric {KEY_UP, "Select previous source line"},
5251480093f4SDimitry Andric {KEY_DOWN, "Select next source line"},
5252af732203SDimitry Andric {KEY_LEFT, "Scroll to the left"},
5253af732203SDimitry Andric {KEY_RIGHT, "Scroll to the right"},
5254480093f4SDimitry Andric {KEY_PPAGE, "Page up"},
5255480093f4SDimitry Andric {KEY_NPAGE, "Page down"},
5256480093f4SDimitry Andric {'b', "Set breakpoint on selected source/disassembly line"},
5257480093f4SDimitry Andric {'c', "Continue process"},
5258480093f4SDimitry Andric {'D', "Detach with process suspended"},
5259480093f4SDimitry Andric {'h', "Show help dialog"},
5260480093f4SDimitry Andric {'n', "Step over (source line)"},
5261480093f4SDimitry Andric {'N', "Step over (single instruction)"},
5262af732203SDimitry Andric {'f', "Step out (finish)"},
5263480093f4SDimitry Andric {'s', "Step in (source line)"},
5264480093f4SDimitry Andric {'S', "Step in (single instruction)"},
5265af732203SDimitry Andric {'u', "Frame up"},
5266af732203SDimitry Andric {'d', "Frame down"},
5267480093f4SDimitry Andric {',', "Page up"},
5268480093f4SDimitry Andric {'.', "Page down"},
5269480093f4SDimitry Andric {'\0', nullptr}};
5270480093f4SDimitry Andric return g_source_view_key_help;
5271480093f4SDimitry Andric }
5272480093f4SDimitry Andric
WindowDelegateDraw(Window & window,bool force)5273480093f4SDimitry Andric bool WindowDelegateDraw(Window &window, bool force) override {
5274480093f4SDimitry Andric ExecutionContext exe_ctx =
5275480093f4SDimitry Andric m_debugger.GetCommandInterpreter().GetExecutionContext();
5276480093f4SDimitry Andric Process *process = exe_ctx.GetProcessPtr();
5277480093f4SDimitry Andric Thread *thread = nullptr;
5278480093f4SDimitry Andric
5279480093f4SDimitry Andric bool update_location = false;
5280480093f4SDimitry Andric if (process) {
5281480093f4SDimitry Andric StateType state = process->GetState();
5282480093f4SDimitry Andric if (StateIsStoppedState(state, true)) {
5283480093f4SDimitry Andric // We are stopped, so it is ok to
5284480093f4SDimitry Andric update_location = true;
5285480093f4SDimitry Andric }
5286480093f4SDimitry Andric }
5287480093f4SDimitry Andric
5288480093f4SDimitry Andric m_min_x = 1;
5289480093f4SDimitry Andric m_min_y = 2;
5290480093f4SDimitry Andric m_max_x = window.GetMaxX() - 1;
5291480093f4SDimitry Andric m_max_y = window.GetMaxY() - 1;
5292480093f4SDimitry Andric
5293480093f4SDimitry Andric const uint32_t num_visible_lines = NumVisibleLines();
5294480093f4SDimitry Andric StackFrameSP frame_sp;
5295480093f4SDimitry Andric bool set_selected_line_to_pc = false;
5296480093f4SDimitry Andric
5297480093f4SDimitry Andric if (update_location) {
5298480093f4SDimitry Andric const bool process_alive = process ? process->IsAlive() : false;
5299480093f4SDimitry Andric bool thread_changed = false;
5300480093f4SDimitry Andric if (process_alive) {
5301480093f4SDimitry Andric thread = exe_ctx.GetThreadPtr();
5302480093f4SDimitry Andric if (thread) {
5303480093f4SDimitry Andric frame_sp = thread->GetSelectedFrame();
5304480093f4SDimitry Andric auto tid = thread->GetID();
5305480093f4SDimitry Andric thread_changed = tid != m_tid;
5306480093f4SDimitry Andric m_tid = tid;
5307480093f4SDimitry Andric } else {
5308480093f4SDimitry Andric if (m_tid != LLDB_INVALID_THREAD_ID) {
5309480093f4SDimitry Andric thread_changed = true;
5310480093f4SDimitry Andric m_tid = LLDB_INVALID_THREAD_ID;
5311480093f4SDimitry Andric }
5312480093f4SDimitry Andric }
5313480093f4SDimitry Andric }
5314480093f4SDimitry Andric const uint32_t stop_id = process ? process->GetStopID() : 0;
5315480093f4SDimitry Andric const bool stop_id_changed = stop_id != m_stop_id;
5316480093f4SDimitry Andric bool frame_changed = false;
5317480093f4SDimitry Andric m_stop_id = stop_id;
5318480093f4SDimitry Andric m_title.Clear();
5319480093f4SDimitry Andric if (frame_sp) {
5320480093f4SDimitry Andric m_sc = frame_sp->GetSymbolContext(eSymbolContextEverything);
5321480093f4SDimitry Andric if (m_sc.module_sp) {
5322480093f4SDimitry Andric m_title.Printf(
5323480093f4SDimitry Andric "%s", m_sc.module_sp->GetFileSpec().GetFilename().GetCString());
5324480093f4SDimitry Andric ConstString func_name = m_sc.GetFunctionName();
5325480093f4SDimitry Andric if (func_name)
5326480093f4SDimitry Andric m_title.Printf("`%s", func_name.GetCString());
5327480093f4SDimitry Andric }
5328480093f4SDimitry Andric const uint32_t frame_idx = frame_sp->GetFrameIndex();
5329480093f4SDimitry Andric frame_changed = frame_idx != m_frame_idx;
5330480093f4SDimitry Andric m_frame_idx = frame_idx;
5331480093f4SDimitry Andric } else {
5332480093f4SDimitry Andric m_sc.Clear(true);
5333480093f4SDimitry Andric frame_changed = m_frame_idx != UINT32_MAX;
5334480093f4SDimitry Andric m_frame_idx = UINT32_MAX;
5335480093f4SDimitry Andric }
5336480093f4SDimitry Andric
5337480093f4SDimitry Andric const bool context_changed =
5338480093f4SDimitry Andric thread_changed || frame_changed || stop_id_changed;
5339480093f4SDimitry Andric
5340480093f4SDimitry Andric if (process_alive) {
5341480093f4SDimitry Andric if (m_sc.line_entry.IsValid()) {
5342480093f4SDimitry Andric m_pc_line = m_sc.line_entry.line;
5343480093f4SDimitry Andric if (m_pc_line != UINT32_MAX)
5344480093f4SDimitry Andric --m_pc_line; // Convert to zero based line number...
5345480093f4SDimitry Andric // Update the selected line if the stop ID changed...
5346480093f4SDimitry Andric if (context_changed)
5347480093f4SDimitry Andric m_selected_line = m_pc_line;
5348480093f4SDimitry Andric
5349480093f4SDimitry Andric if (m_file_sp && m_file_sp->GetFileSpec() == m_sc.line_entry.file) {
5350480093f4SDimitry Andric // Same file, nothing to do, we should either have the lines or not
5351480093f4SDimitry Andric // (source file missing)
5352480093f4SDimitry Andric if (m_selected_line >= static_cast<size_t>(m_first_visible_line)) {
5353480093f4SDimitry Andric if (m_selected_line >= m_first_visible_line + num_visible_lines)
5354480093f4SDimitry Andric m_first_visible_line = m_selected_line - 10;
5355480093f4SDimitry Andric } else {
5356480093f4SDimitry Andric if (m_selected_line > 10)
5357480093f4SDimitry Andric m_first_visible_line = m_selected_line - 10;
5358480093f4SDimitry Andric else
5359480093f4SDimitry Andric m_first_visible_line = 0;
5360480093f4SDimitry Andric }
5361480093f4SDimitry Andric } else {
5362480093f4SDimitry Andric // File changed, set selected line to the line with the PC
5363480093f4SDimitry Andric m_selected_line = m_pc_line;
5364480093f4SDimitry Andric m_file_sp =
5365480093f4SDimitry Andric m_debugger.GetSourceManager().GetFile(m_sc.line_entry.file);
5366480093f4SDimitry Andric if (m_file_sp) {
5367480093f4SDimitry Andric const size_t num_lines = m_file_sp->GetNumLines();
5368480093f4SDimitry Andric m_line_width = 1;
5369480093f4SDimitry Andric for (size_t n = num_lines; n >= 10; n = n / 10)
5370480093f4SDimitry Andric ++m_line_width;
5371480093f4SDimitry Andric
5372480093f4SDimitry Andric if (num_lines < num_visible_lines ||
5373480093f4SDimitry Andric m_selected_line < num_visible_lines)
5374480093f4SDimitry Andric m_first_visible_line = 0;
5375480093f4SDimitry Andric else
5376480093f4SDimitry Andric m_first_visible_line = m_selected_line - 10;
5377480093f4SDimitry Andric }
5378480093f4SDimitry Andric }
5379480093f4SDimitry Andric } else {
5380480093f4SDimitry Andric m_file_sp.reset();
5381480093f4SDimitry Andric }
5382480093f4SDimitry Andric
5383480093f4SDimitry Andric if (!m_file_sp || m_file_sp->GetNumLines() == 0) {
5384480093f4SDimitry Andric // Show disassembly
5385480093f4SDimitry Andric bool prefer_file_cache = false;
5386480093f4SDimitry Andric if (m_sc.function) {
5387480093f4SDimitry Andric if (m_disassembly_scope != m_sc.function) {
5388480093f4SDimitry Andric m_disassembly_scope = m_sc.function;
5389480093f4SDimitry Andric m_disassembly_sp = m_sc.function->GetInstructions(
5390*5f7ddb14SDimitry Andric exe_ctx, nullptr, !prefer_file_cache);
5391480093f4SDimitry Andric if (m_disassembly_sp) {
5392480093f4SDimitry Andric set_selected_line_to_pc = true;
5393480093f4SDimitry Andric m_disassembly_range = m_sc.function->GetAddressRange();
5394480093f4SDimitry Andric } else {
5395480093f4SDimitry Andric m_disassembly_range.Clear();
5396480093f4SDimitry Andric }
5397480093f4SDimitry Andric } else {
5398480093f4SDimitry Andric set_selected_line_to_pc = context_changed;
5399480093f4SDimitry Andric }
5400480093f4SDimitry Andric } else if (m_sc.symbol) {
5401480093f4SDimitry Andric if (m_disassembly_scope != m_sc.symbol) {
5402480093f4SDimitry Andric m_disassembly_scope = m_sc.symbol;
5403480093f4SDimitry Andric m_disassembly_sp = m_sc.symbol->GetInstructions(
5404480093f4SDimitry Andric exe_ctx, nullptr, prefer_file_cache);
5405480093f4SDimitry Andric if (m_disassembly_sp) {
5406480093f4SDimitry Andric set_selected_line_to_pc = true;
5407480093f4SDimitry Andric m_disassembly_range.GetBaseAddress() =
5408480093f4SDimitry Andric m_sc.symbol->GetAddress();
5409480093f4SDimitry Andric m_disassembly_range.SetByteSize(m_sc.symbol->GetByteSize());
5410480093f4SDimitry Andric } else {
5411480093f4SDimitry Andric m_disassembly_range.Clear();
5412480093f4SDimitry Andric }
5413480093f4SDimitry Andric } else {
5414480093f4SDimitry Andric set_selected_line_to_pc = context_changed;
5415480093f4SDimitry Andric }
5416480093f4SDimitry Andric }
5417480093f4SDimitry Andric }
5418480093f4SDimitry Andric } else {
5419480093f4SDimitry Andric m_pc_line = UINT32_MAX;
5420480093f4SDimitry Andric }
5421480093f4SDimitry Andric }
5422480093f4SDimitry Andric
5423480093f4SDimitry Andric const int window_width = window.GetWidth();
5424480093f4SDimitry Andric window.Erase();
5425480093f4SDimitry Andric window.DrawTitleBox("Sources");
5426480093f4SDimitry Andric if (!m_title.GetString().empty()) {
5427480093f4SDimitry Andric window.AttributeOn(A_REVERSE);
5428480093f4SDimitry Andric window.MoveCursor(1, 1);
5429480093f4SDimitry Andric window.PutChar(' ');
5430af732203SDimitry Andric window.PutCStringTruncated(1, m_title.GetString().str().c_str());
5431480093f4SDimitry Andric int x = window.GetCursorX();
5432480093f4SDimitry Andric if (x < window_width - 1) {
5433480093f4SDimitry Andric window.Printf("%*s", window_width - x - 1, "");
5434480093f4SDimitry Andric }
5435480093f4SDimitry Andric window.AttributeOff(A_REVERSE);
5436480093f4SDimitry Andric }
5437480093f4SDimitry Andric
5438480093f4SDimitry Andric Target *target = exe_ctx.GetTargetPtr();
5439480093f4SDimitry Andric const size_t num_source_lines = GetNumSourceLines();
5440480093f4SDimitry Andric if (num_source_lines > 0) {
5441480093f4SDimitry Andric // Display source
5442480093f4SDimitry Andric BreakpointLines bp_lines;
5443480093f4SDimitry Andric if (target) {
5444480093f4SDimitry Andric BreakpointList &bp_list = target->GetBreakpointList();
5445480093f4SDimitry Andric const size_t num_bps = bp_list.GetSize();
5446480093f4SDimitry Andric for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
5447480093f4SDimitry Andric BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
5448480093f4SDimitry Andric const size_t num_bps_locs = bp_sp->GetNumLocations();
5449480093f4SDimitry Andric for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) {
5450480093f4SDimitry Andric BreakpointLocationSP bp_loc_sp =
5451480093f4SDimitry Andric bp_sp->GetLocationAtIndex(bp_loc_idx);
5452480093f4SDimitry Andric LineEntry bp_loc_line_entry;
5453480093f4SDimitry Andric if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry(
5454480093f4SDimitry Andric bp_loc_line_entry)) {
5455480093f4SDimitry Andric if (m_file_sp->GetFileSpec() == bp_loc_line_entry.file) {
5456480093f4SDimitry Andric bp_lines.insert(bp_loc_line_entry.line);
5457480093f4SDimitry Andric }
5458480093f4SDimitry Andric }
5459480093f4SDimitry Andric }
5460480093f4SDimitry Andric }
5461480093f4SDimitry Andric }
5462480093f4SDimitry Andric
5463480093f4SDimitry Andric const attr_t selected_highlight_attr = A_REVERSE;
5464af732203SDimitry Andric const attr_t pc_highlight_attr = COLOR_PAIR(BlackOnBlue);
5465480093f4SDimitry Andric
5466480093f4SDimitry Andric for (size_t i = 0; i < num_visible_lines; ++i) {
5467480093f4SDimitry Andric const uint32_t curr_line = m_first_visible_line + i;
5468480093f4SDimitry Andric if (curr_line < num_source_lines) {
5469480093f4SDimitry Andric const int line_y = m_min_y + i;
5470480093f4SDimitry Andric window.MoveCursor(1, line_y);
5471480093f4SDimitry Andric const bool is_pc_line = curr_line == m_pc_line;
5472480093f4SDimitry Andric const bool line_is_selected = m_selected_line == curr_line;
5473480093f4SDimitry Andric // Highlight the line as the PC line first, then if the selected line
5474480093f4SDimitry Andric // isn't the same as the PC line, highlight it differently
5475480093f4SDimitry Andric attr_t highlight_attr = 0;
5476480093f4SDimitry Andric attr_t bp_attr = 0;
5477480093f4SDimitry Andric if (is_pc_line)
5478480093f4SDimitry Andric highlight_attr = pc_highlight_attr;
5479480093f4SDimitry Andric else if (line_is_selected)
5480480093f4SDimitry Andric highlight_attr = selected_highlight_attr;
5481480093f4SDimitry Andric
5482480093f4SDimitry Andric if (bp_lines.find(curr_line + 1) != bp_lines.end())
5483af732203SDimitry Andric bp_attr = COLOR_PAIR(BlackOnWhite);
5484480093f4SDimitry Andric
5485480093f4SDimitry Andric if (bp_attr)
5486480093f4SDimitry Andric window.AttributeOn(bp_attr);
5487480093f4SDimitry Andric
5488480093f4SDimitry Andric window.Printf(" %*u ", m_line_width, curr_line + 1);
5489480093f4SDimitry Andric
5490480093f4SDimitry Andric if (bp_attr)
5491480093f4SDimitry Andric window.AttributeOff(bp_attr);
5492480093f4SDimitry Andric
5493480093f4SDimitry Andric window.PutChar(ACS_VLINE);
5494480093f4SDimitry Andric // Mark the line with the PC with a diamond
5495480093f4SDimitry Andric if (is_pc_line)
5496480093f4SDimitry Andric window.PutChar(ACS_DIAMOND);
5497480093f4SDimitry Andric else
5498480093f4SDimitry Andric window.PutChar(' ');
5499480093f4SDimitry Andric
5500480093f4SDimitry Andric if (highlight_attr)
5501480093f4SDimitry Andric window.AttributeOn(highlight_attr);
5502af732203SDimitry Andric
5503af732203SDimitry Andric StreamString lineStream;
5504af732203SDimitry Andric m_file_sp->DisplaySourceLines(curr_line + 1, {}, 0, 0, &lineStream);
5505af732203SDimitry Andric StringRef line = lineStream.GetString();
5506af732203SDimitry Andric if (line.endswith("\n"))
5507af732203SDimitry Andric line = line.drop_back();
5508af732203SDimitry Andric bool wasWritten = window.OutputColoredStringTruncated(
5509af732203SDimitry Andric 1, line, m_first_visible_column, line_is_selected);
5510af732203SDimitry Andric if (line_is_selected && !wasWritten) {
5511af732203SDimitry Andric // Draw an empty space to show the selected line if empty,
5512af732203SDimitry Andric // or draw '<' if nothing is visible because of scrolling too much
5513af732203SDimitry Andric // to the right.
5514af732203SDimitry Andric window.PutCStringTruncated(
5515af732203SDimitry Andric 1, line.empty() && m_first_visible_column == 0 ? " " : "<");
5516af732203SDimitry Andric }
5517480093f4SDimitry Andric
5518480093f4SDimitry Andric if (is_pc_line && frame_sp &&
5519480093f4SDimitry Andric frame_sp->GetConcreteFrameIndex() == 0) {
5520480093f4SDimitry Andric StopInfoSP stop_info_sp;
5521480093f4SDimitry Andric if (thread)
5522480093f4SDimitry Andric stop_info_sp = thread->GetStopInfo();
5523480093f4SDimitry Andric if (stop_info_sp) {
5524480093f4SDimitry Andric const char *stop_description = stop_info_sp->GetDescription();
5525480093f4SDimitry Andric if (stop_description && stop_description[0]) {
5526480093f4SDimitry Andric size_t stop_description_len = strlen(stop_description);
5527480093f4SDimitry Andric int desc_x = window_width - stop_description_len - 16;
5528af732203SDimitry Andric if (desc_x - window.GetCursorX() > 0)
5529480093f4SDimitry Andric window.Printf("%*s", desc_x - window.GetCursorX(), "");
5530af732203SDimitry Andric window.MoveCursor(window_width - stop_description_len - 16,
5531af732203SDimitry Andric line_y);
5532af732203SDimitry Andric const attr_t stop_reason_attr = COLOR_PAIR(WhiteOnBlue);
5533af732203SDimitry Andric window.AttributeOn(stop_reason_attr);
5534af732203SDimitry Andric window.PrintfTruncated(1, " <<< Thread %u: %s ",
5535af732203SDimitry Andric thread->GetIndexID(), stop_description);
5536af732203SDimitry Andric window.AttributeOff(stop_reason_attr);
5537480093f4SDimitry Andric }
5538480093f4SDimitry Andric } else {
5539480093f4SDimitry Andric window.Printf("%*s", window_width - window.GetCursorX() - 1, "");
5540480093f4SDimitry Andric }
5541480093f4SDimitry Andric }
5542480093f4SDimitry Andric if (highlight_attr)
5543480093f4SDimitry Andric window.AttributeOff(highlight_attr);
5544480093f4SDimitry Andric } else {
5545480093f4SDimitry Andric break;
5546480093f4SDimitry Andric }
5547480093f4SDimitry Andric }
5548480093f4SDimitry Andric } else {
5549480093f4SDimitry Andric size_t num_disassembly_lines = GetNumDisassemblyLines();
5550480093f4SDimitry Andric if (num_disassembly_lines > 0) {
5551480093f4SDimitry Andric // Display disassembly
5552480093f4SDimitry Andric BreakpointAddrs bp_file_addrs;
5553480093f4SDimitry Andric Target *target = exe_ctx.GetTargetPtr();
5554480093f4SDimitry Andric if (target) {
5555480093f4SDimitry Andric BreakpointList &bp_list = target->GetBreakpointList();
5556480093f4SDimitry Andric const size_t num_bps = bp_list.GetSize();
5557480093f4SDimitry Andric for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
5558480093f4SDimitry Andric BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
5559480093f4SDimitry Andric const size_t num_bps_locs = bp_sp->GetNumLocations();
5560480093f4SDimitry Andric for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs;
5561480093f4SDimitry Andric ++bp_loc_idx) {
5562480093f4SDimitry Andric BreakpointLocationSP bp_loc_sp =
5563480093f4SDimitry Andric bp_sp->GetLocationAtIndex(bp_loc_idx);
5564480093f4SDimitry Andric LineEntry bp_loc_line_entry;
5565480093f4SDimitry Andric const lldb::addr_t file_addr =
5566480093f4SDimitry Andric bp_loc_sp->GetAddress().GetFileAddress();
5567480093f4SDimitry Andric if (file_addr != LLDB_INVALID_ADDRESS) {
5568480093f4SDimitry Andric if (m_disassembly_range.ContainsFileAddress(file_addr))
5569480093f4SDimitry Andric bp_file_addrs.insert(file_addr);
5570480093f4SDimitry Andric }
5571480093f4SDimitry Andric }
5572480093f4SDimitry Andric }
5573480093f4SDimitry Andric }
5574480093f4SDimitry Andric
5575480093f4SDimitry Andric const attr_t selected_highlight_attr = A_REVERSE;
5576af732203SDimitry Andric const attr_t pc_highlight_attr = COLOR_PAIR(WhiteOnBlue);
5577480093f4SDimitry Andric
5578480093f4SDimitry Andric StreamString strm;
5579480093f4SDimitry Andric
5580480093f4SDimitry Andric InstructionList &insts = m_disassembly_sp->GetInstructionList();
5581480093f4SDimitry Andric Address pc_address;
5582480093f4SDimitry Andric
5583480093f4SDimitry Andric if (frame_sp)
5584480093f4SDimitry Andric pc_address = frame_sp->GetFrameCodeAddress();
5585480093f4SDimitry Andric const uint32_t pc_idx =
5586480093f4SDimitry Andric pc_address.IsValid()
5587480093f4SDimitry Andric ? insts.GetIndexOfInstructionAtAddress(pc_address)
5588480093f4SDimitry Andric : UINT32_MAX;
5589480093f4SDimitry Andric if (set_selected_line_to_pc) {
5590480093f4SDimitry Andric m_selected_line = pc_idx;
5591480093f4SDimitry Andric }
5592480093f4SDimitry Andric
5593480093f4SDimitry Andric const uint32_t non_visible_pc_offset = (num_visible_lines / 5);
5594480093f4SDimitry Andric if (static_cast<size_t>(m_first_visible_line) >= num_disassembly_lines)
5595480093f4SDimitry Andric m_first_visible_line = 0;
5596480093f4SDimitry Andric
5597480093f4SDimitry Andric if (pc_idx < num_disassembly_lines) {
5598480093f4SDimitry Andric if (pc_idx < static_cast<uint32_t>(m_first_visible_line) ||
5599480093f4SDimitry Andric pc_idx >= m_first_visible_line + num_visible_lines)
5600480093f4SDimitry Andric m_first_visible_line = pc_idx - non_visible_pc_offset;
5601480093f4SDimitry Andric }
5602480093f4SDimitry Andric
5603480093f4SDimitry Andric for (size_t i = 0; i < num_visible_lines; ++i) {
5604480093f4SDimitry Andric const uint32_t inst_idx = m_first_visible_line + i;
5605480093f4SDimitry Andric Instruction *inst = insts.GetInstructionAtIndex(inst_idx).get();
5606480093f4SDimitry Andric if (!inst)
5607480093f4SDimitry Andric break;
5608480093f4SDimitry Andric
5609480093f4SDimitry Andric const int line_y = m_min_y + i;
5610480093f4SDimitry Andric window.MoveCursor(1, line_y);
5611480093f4SDimitry Andric const bool is_pc_line = frame_sp && inst_idx == pc_idx;
5612480093f4SDimitry Andric const bool line_is_selected = m_selected_line == inst_idx;
5613480093f4SDimitry Andric // Highlight the line as the PC line first, then if the selected line
5614480093f4SDimitry Andric // isn't the same as the PC line, highlight it differently
5615480093f4SDimitry Andric attr_t highlight_attr = 0;
5616480093f4SDimitry Andric attr_t bp_attr = 0;
5617480093f4SDimitry Andric if (is_pc_line)
5618480093f4SDimitry Andric highlight_attr = pc_highlight_attr;
5619480093f4SDimitry Andric else if (line_is_selected)
5620480093f4SDimitry Andric highlight_attr = selected_highlight_attr;
5621480093f4SDimitry Andric
5622480093f4SDimitry Andric if (bp_file_addrs.find(inst->GetAddress().GetFileAddress()) !=
5623480093f4SDimitry Andric bp_file_addrs.end())
5624af732203SDimitry Andric bp_attr = COLOR_PAIR(BlackOnWhite);
5625480093f4SDimitry Andric
5626480093f4SDimitry Andric if (bp_attr)
5627480093f4SDimitry Andric window.AttributeOn(bp_attr);
5628480093f4SDimitry Andric
5629480093f4SDimitry Andric window.Printf(" 0x%16.16llx ",
5630480093f4SDimitry Andric static_cast<unsigned long long>(
5631480093f4SDimitry Andric inst->GetAddress().GetLoadAddress(target)));
5632480093f4SDimitry Andric
5633480093f4SDimitry Andric if (bp_attr)
5634480093f4SDimitry Andric window.AttributeOff(bp_attr);
5635480093f4SDimitry Andric
5636480093f4SDimitry Andric window.PutChar(ACS_VLINE);
5637480093f4SDimitry Andric // Mark the line with the PC with a diamond
5638480093f4SDimitry Andric if (is_pc_line)
5639480093f4SDimitry Andric window.PutChar(ACS_DIAMOND);
5640480093f4SDimitry Andric else
5641480093f4SDimitry Andric window.PutChar(' ');
5642480093f4SDimitry Andric
5643480093f4SDimitry Andric if (highlight_attr)
5644480093f4SDimitry Andric window.AttributeOn(highlight_attr);
5645480093f4SDimitry Andric
5646480093f4SDimitry Andric const char *mnemonic = inst->GetMnemonic(&exe_ctx);
5647480093f4SDimitry Andric const char *operands = inst->GetOperands(&exe_ctx);
5648480093f4SDimitry Andric const char *comment = inst->GetComment(&exe_ctx);
5649480093f4SDimitry Andric
5650480093f4SDimitry Andric if (mnemonic != nullptr && mnemonic[0] == '\0')
5651480093f4SDimitry Andric mnemonic = nullptr;
5652480093f4SDimitry Andric if (operands != nullptr && operands[0] == '\0')
5653480093f4SDimitry Andric operands = nullptr;
5654480093f4SDimitry Andric if (comment != nullptr && comment[0] == '\0')
5655480093f4SDimitry Andric comment = nullptr;
5656480093f4SDimitry Andric
5657480093f4SDimitry Andric strm.Clear();
5658480093f4SDimitry Andric
5659480093f4SDimitry Andric if (mnemonic != nullptr && operands != nullptr && comment != nullptr)
5660480093f4SDimitry Andric strm.Printf("%-8s %-25s ; %s", mnemonic, operands, comment);
5661480093f4SDimitry Andric else if (mnemonic != nullptr && operands != nullptr)
5662480093f4SDimitry Andric strm.Printf("%-8s %s", mnemonic, operands);
5663480093f4SDimitry Andric else if (mnemonic != nullptr)
5664480093f4SDimitry Andric strm.Printf("%s", mnemonic);
5665480093f4SDimitry Andric
5666480093f4SDimitry Andric int right_pad = 1;
5667af732203SDimitry Andric window.PutCStringTruncated(
5668af732203SDimitry Andric right_pad,
5669af732203SDimitry Andric strm.GetString().substr(m_first_visible_column).data());
5670480093f4SDimitry Andric
5671480093f4SDimitry Andric if (is_pc_line && frame_sp &&
5672480093f4SDimitry Andric frame_sp->GetConcreteFrameIndex() == 0) {
5673480093f4SDimitry Andric StopInfoSP stop_info_sp;
5674480093f4SDimitry Andric if (thread)
5675480093f4SDimitry Andric stop_info_sp = thread->GetStopInfo();
5676480093f4SDimitry Andric if (stop_info_sp) {
5677480093f4SDimitry Andric const char *stop_description = stop_info_sp->GetDescription();
5678480093f4SDimitry Andric if (stop_description && stop_description[0]) {
5679480093f4SDimitry Andric size_t stop_description_len = strlen(stop_description);
5680480093f4SDimitry Andric int desc_x = window_width - stop_description_len - 16;
5681af732203SDimitry Andric if (desc_x - window.GetCursorX() > 0)
5682480093f4SDimitry Andric window.Printf("%*s", desc_x - window.GetCursorX(), "");
5683af732203SDimitry Andric window.MoveCursor(window_width - stop_description_len - 15,
5684af732203SDimitry Andric line_y);
5685af732203SDimitry Andric window.PrintfTruncated(1, "<<< Thread %u: %s ",
5686af732203SDimitry Andric thread->GetIndexID(), stop_description);
5687480093f4SDimitry Andric }
5688480093f4SDimitry Andric } else {
5689480093f4SDimitry Andric window.Printf("%*s", window_width - window.GetCursorX() - 1, "");
5690480093f4SDimitry Andric }
5691480093f4SDimitry Andric }
5692480093f4SDimitry Andric if (highlight_attr)
5693480093f4SDimitry Andric window.AttributeOff(highlight_attr);
5694480093f4SDimitry Andric }
5695480093f4SDimitry Andric }
5696480093f4SDimitry Andric }
5697480093f4SDimitry Andric return true; // Drawing handled
5698480093f4SDimitry Andric }
5699480093f4SDimitry Andric
GetNumLines()5700480093f4SDimitry Andric size_t GetNumLines() {
5701480093f4SDimitry Andric size_t num_lines = GetNumSourceLines();
5702480093f4SDimitry Andric if (num_lines == 0)
5703480093f4SDimitry Andric num_lines = GetNumDisassemblyLines();
5704480093f4SDimitry Andric return num_lines;
5705480093f4SDimitry Andric }
5706480093f4SDimitry Andric
GetNumSourceLines() const5707480093f4SDimitry Andric size_t GetNumSourceLines() const {
5708480093f4SDimitry Andric if (m_file_sp)
5709480093f4SDimitry Andric return m_file_sp->GetNumLines();
5710480093f4SDimitry Andric return 0;
5711480093f4SDimitry Andric }
5712480093f4SDimitry Andric
GetNumDisassemblyLines() const5713480093f4SDimitry Andric size_t GetNumDisassemblyLines() const {
5714480093f4SDimitry Andric if (m_disassembly_sp)
5715480093f4SDimitry Andric return m_disassembly_sp->GetInstructionList().GetSize();
5716480093f4SDimitry Andric return 0;
5717480093f4SDimitry Andric }
5718480093f4SDimitry Andric
WindowDelegateHandleChar(Window & window,int c)5719480093f4SDimitry Andric HandleCharResult WindowDelegateHandleChar(Window &window, int c) override {
5720480093f4SDimitry Andric const uint32_t num_visible_lines = NumVisibleLines();
5721480093f4SDimitry Andric const size_t num_lines = GetNumLines();
5722480093f4SDimitry Andric
5723480093f4SDimitry Andric switch (c) {
5724480093f4SDimitry Andric case ',':
5725480093f4SDimitry Andric case KEY_PPAGE:
5726480093f4SDimitry Andric // Page up key
5727480093f4SDimitry Andric if (static_cast<uint32_t>(m_first_visible_line) > num_visible_lines)
5728480093f4SDimitry Andric m_first_visible_line -= num_visible_lines;
5729480093f4SDimitry Andric else
5730480093f4SDimitry Andric m_first_visible_line = 0;
5731480093f4SDimitry Andric m_selected_line = m_first_visible_line;
5732480093f4SDimitry Andric return eKeyHandled;
5733480093f4SDimitry Andric
5734480093f4SDimitry Andric case '.':
5735480093f4SDimitry Andric case KEY_NPAGE:
5736480093f4SDimitry Andric // Page down key
5737480093f4SDimitry Andric {
5738480093f4SDimitry Andric if (m_first_visible_line + num_visible_lines < num_lines)
5739480093f4SDimitry Andric m_first_visible_line += num_visible_lines;
5740480093f4SDimitry Andric else if (num_lines < num_visible_lines)
5741480093f4SDimitry Andric m_first_visible_line = 0;
5742480093f4SDimitry Andric else
5743480093f4SDimitry Andric m_first_visible_line = num_lines - num_visible_lines;
5744480093f4SDimitry Andric m_selected_line = m_first_visible_line;
5745480093f4SDimitry Andric }
5746480093f4SDimitry Andric return eKeyHandled;
5747480093f4SDimitry Andric
5748480093f4SDimitry Andric case KEY_UP:
5749480093f4SDimitry Andric if (m_selected_line > 0) {
5750480093f4SDimitry Andric m_selected_line--;
5751480093f4SDimitry Andric if (static_cast<size_t>(m_first_visible_line) > m_selected_line)
5752480093f4SDimitry Andric m_first_visible_line = m_selected_line;
5753480093f4SDimitry Andric }
5754480093f4SDimitry Andric return eKeyHandled;
5755480093f4SDimitry Andric
5756480093f4SDimitry Andric case KEY_DOWN:
5757480093f4SDimitry Andric if (m_selected_line + 1 < num_lines) {
5758480093f4SDimitry Andric m_selected_line++;
5759480093f4SDimitry Andric if (m_first_visible_line + num_visible_lines < m_selected_line)
5760480093f4SDimitry Andric m_first_visible_line++;
5761480093f4SDimitry Andric }
5762480093f4SDimitry Andric return eKeyHandled;
5763480093f4SDimitry Andric
5764af732203SDimitry Andric case KEY_LEFT:
5765af732203SDimitry Andric if (m_first_visible_column > 0)
5766af732203SDimitry Andric --m_first_visible_column;
5767af732203SDimitry Andric return eKeyHandled;
5768af732203SDimitry Andric
5769af732203SDimitry Andric case KEY_RIGHT:
5770af732203SDimitry Andric ++m_first_visible_column;
5771af732203SDimitry Andric return eKeyHandled;
5772af732203SDimitry Andric
5773480093f4SDimitry Andric case '\r':
5774480093f4SDimitry Andric case '\n':
5775480093f4SDimitry Andric case KEY_ENTER:
5776480093f4SDimitry Andric // Set a breakpoint and run to the line using a one shot breakpoint
5777480093f4SDimitry Andric if (GetNumSourceLines() > 0) {
5778480093f4SDimitry Andric ExecutionContext exe_ctx =
5779480093f4SDimitry Andric m_debugger.GetCommandInterpreter().GetExecutionContext();
5780480093f4SDimitry Andric if (exe_ctx.HasProcessScope() && exe_ctx.GetProcessRef().IsAlive()) {
5781480093f4SDimitry Andric BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint(
5782480093f4SDimitry Andric nullptr, // Don't limit the breakpoint to certain modules
5783480093f4SDimitry Andric m_file_sp->GetFileSpec(), // Source file
5784480093f4SDimitry Andric m_selected_line +
5785480093f4SDimitry Andric 1, // Source line number (m_selected_line is zero based)
5786480093f4SDimitry Andric 0, // Unspecified column.
5787480093f4SDimitry Andric 0, // No offset
5788480093f4SDimitry Andric eLazyBoolCalculate, // Check inlines using global setting
5789480093f4SDimitry Andric eLazyBoolCalculate, // Skip prologue using global setting,
5790480093f4SDimitry Andric false, // internal
5791480093f4SDimitry Andric false, // request_hardware
5792480093f4SDimitry Andric eLazyBoolCalculate); // move_to_nearest_code
5793480093f4SDimitry Andric // Make breakpoint one shot
5794*5f7ddb14SDimitry Andric bp_sp->GetOptions().SetOneShot(true);
5795480093f4SDimitry Andric exe_ctx.GetProcessRef().Resume();
5796480093f4SDimitry Andric }
5797480093f4SDimitry Andric } else if (m_selected_line < GetNumDisassemblyLines()) {
5798480093f4SDimitry Andric const Instruction *inst = m_disassembly_sp->GetInstructionList()
5799480093f4SDimitry Andric .GetInstructionAtIndex(m_selected_line)
5800480093f4SDimitry Andric .get();
5801480093f4SDimitry Andric ExecutionContext exe_ctx =
5802480093f4SDimitry Andric m_debugger.GetCommandInterpreter().GetExecutionContext();
5803480093f4SDimitry Andric if (exe_ctx.HasTargetScope()) {
5804480093f4SDimitry Andric Address addr = inst->GetAddress();
5805480093f4SDimitry Andric BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint(
5806480093f4SDimitry Andric addr, // lldb_private::Address
5807480093f4SDimitry Andric false, // internal
5808480093f4SDimitry Andric false); // request_hardware
5809480093f4SDimitry Andric // Make breakpoint one shot
5810*5f7ddb14SDimitry Andric bp_sp->GetOptions().SetOneShot(true);
5811480093f4SDimitry Andric exe_ctx.GetProcessRef().Resume();
5812480093f4SDimitry Andric }
5813480093f4SDimitry Andric }
5814480093f4SDimitry Andric return eKeyHandled;
5815480093f4SDimitry Andric
5816480093f4SDimitry Andric case 'b': // 'b' == toggle breakpoint on currently selected line
5817af732203SDimitry Andric ToggleBreakpointOnSelectedLine();
5818480093f4SDimitry Andric return eKeyHandled;
5819480093f4SDimitry Andric
5820480093f4SDimitry Andric case 'D': // 'D' == detach and keep stopped
5821480093f4SDimitry Andric {
5822480093f4SDimitry Andric ExecutionContext exe_ctx =
5823480093f4SDimitry Andric m_debugger.GetCommandInterpreter().GetExecutionContext();
5824480093f4SDimitry Andric if (exe_ctx.HasProcessScope())
5825af732203SDimitry Andric exe_ctx.GetProcessRef().Detach(true);
5826480093f4SDimitry Andric }
5827480093f4SDimitry Andric return eKeyHandled;
5828480093f4SDimitry Andric
5829480093f4SDimitry Andric case 'c':
5830480093f4SDimitry Andric // 'c' == continue
5831480093f4SDimitry Andric {
5832480093f4SDimitry Andric ExecutionContext exe_ctx =
5833480093f4SDimitry Andric m_debugger.GetCommandInterpreter().GetExecutionContext();
5834480093f4SDimitry Andric if (exe_ctx.HasProcessScope())
5835480093f4SDimitry Andric exe_ctx.GetProcessRef().Resume();
5836480093f4SDimitry Andric }
5837480093f4SDimitry Andric return eKeyHandled;
5838480093f4SDimitry Andric
5839af732203SDimitry Andric case 'f':
5840af732203SDimitry Andric // 'f' == step out (finish)
5841480093f4SDimitry Andric {
5842480093f4SDimitry Andric ExecutionContext exe_ctx =
5843480093f4SDimitry Andric m_debugger.GetCommandInterpreter().GetExecutionContext();
5844480093f4SDimitry Andric if (exe_ctx.HasThreadScope() &&
5845480093f4SDimitry Andric StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) {
5846480093f4SDimitry Andric exe_ctx.GetThreadRef().StepOut();
5847480093f4SDimitry Andric }
5848480093f4SDimitry Andric }
5849480093f4SDimitry Andric return eKeyHandled;
5850480093f4SDimitry Andric
5851480093f4SDimitry Andric case 'n': // 'n' == step over
5852480093f4SDimitry Andric case 'N': // 'N' == step over instruction
5853480093f4SDimitry Andric {
5854480093f4SDimitry Andric ExecutionContext exe_ctx =
5855480093f4SDimitry Andric m_debugger.GetCommandInterpreter().GetExecutionContext();
5856480093f4SDimitry Andric if (exe_ctx.HasThreadScope() &&
5857480093f4SDimitry Andric StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) {
5858480093f4SDimitry Andric bool source_step = (c == 'n');
5859480093f4SDimitry Andric exe_ctx.GetThreadRef().StepOver(source_step);
5860480093f4SDimitry Andric }
5861480093f4SDimitry Andric }
5862480093f4SDimitry Andric return eKeyHandled;
5863480093f4SDimitry Andric
5864480093f4SDimitry Andric case 's': // 's' == step into
5865480093f4SDimitry Andric case 'S': // 'S' == step into instruction
5866480093f4SDimitry Andric {
5867480093f4SDimitry Andric ExecutionContext exe_ctx =
5868480093f4SDimitry Andric m_debugger.GetCommandInterpreter().GetExecutionContext();
5869480093f4SDimitry Andric if (exe_ctx.HasThreadScope() &&
5870480093f4SDimitry Andric StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) {
5871480093f4SDimitry Andric bool source_step = (c == 's');
5872480093f4SDimitry Andric exe_ctx.GetThreadRef().StepIn(source_step);
5873480093f4SDimitry Andric }
5874480093f4SDimitry Andric }
5875480093f4SDimitry Andric return eKeyHandled;
5876480093f4SDimitry Andric
5877af732203SDimitry Andric case 'u': // 'u' == frame up
5878af732203SDimitry Andric case 'd': // 'd' == frame down
5879af732203SDimitry Andric {
5880af732203SDimitry Andric ExecutionContext exe_ctx =
5881af732203SDimitry Andric m_debugger.GetCommandInterpreter().GetExecutionContext();
5882af732203SDimitry Andric if (exe_ctx.HasThreadScope()) {
5883af732203SDimitry Andric Thread *thread = exe_ctx.GetThreadPtr();
5884af732203SDimitry Andric uint32_t frame_idx = thread->GetSelectedFrameIndex();
5885af732203SDimitry Andric if (frame_idx == UINT32_MAX)
5886af732203SDimitry Andric frame_idx = 0;
5887af732203SDimitry Andric if (c == 'u' && frame_idx + 1 < thread->GetStackFrameCount())
5888af732203SDimitry Andric ++frame_idx;
5889af732203SDimitry Andric else if (c == 'd' && frame_idx > 0)
5890af732203SDimitry Andric --frame_idx;
5891af732203SDimitry Andric if (thread->SetSelectedFrameByIndex(frame_idx, true))
5892af732203SDimitry Andric exe_ctx.SetFrameSP(thread->GetSelectedFrame());
5893af732203SDimitry Andric }
5894af732203SDimitry Andric }
5895af732203SDimitry Andric return eKeyHandled;
5896af732203SDimitry Andric
5897480093f4SDimitry Andric case 'h':
5898480093f4SDimitry Andric window.CreateHelpSubwindow();
5899480093f4SDimitry Andric return eKeyHandled;
5900480093f4SDimitry Andric
5901480093f4SDimitry Andric default:
5902480093f4SDimitry Andric break;
5903480093f4SDimitry Andric }
5904480093f4SDimitry Andric return eKeyNotHandled;
5905480093f4SDimitry Andric }
5906480093f4SDimitry Andric
ToggleBreakpointOnSelectedLine()5907af732203SDimitry Andric void ToggleBreakpointOnSelectedLine() {
5908af732203SDimitry Andric ExecutionContext exe_ctx =
5909af732203SDimitry Andric m_debugger.GetCommandInterpreter().GetExecutionContext();
5910af732203SDimitry Andric if (!exe_ctx.HasTargetScope())
5911af732203SDimitry Andric return;
5912af732203SDimitry Andric if (GetNumSourceLines() > 0) {
5913af732203SDimitry Andric // Source file breakpoint.
5914af732203SDimitry Andric BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList();
5915af732203SDimitry Andric const size_t num_bps = bp_list.GetSize();
5916af732203SDimitry Andric for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
5917af732203SDimitry Andric BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
5918af732203SDimitry Andric const size_t num_bps_locs = bp_sp->GetNumLocations();
5919af732203SDimitry Andric for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) {
5920af732203SDimitry Andric BreakpointLocationSP bp_loc_sp =
5921af732203SDimitry Andric bp_sp->GetLocationAtIndex(bp_loc_idx);
5922af732203SDimitry Andric LineEntry bp_loc_line_entry;
5923af732203SDimitry Andric if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry(
5924af732203SDimitry Andric bp_loc_line_entry)) {
5925af732203SDimitry Andric if (m_file_sp->GetFileSpec() == bp_loc_line_entry.file &&
5926af732203SDimitry Andric m_selected_line + 1 == bp_loc_line_entry.line) {
5927af732203SDimitry Andric bool removed =
5928af732203SDimitry Andric exe_ctx.GetTargetRef().RemoveBreakpointByID(bp_sp->GetID());
5929af732203SDimitry Andric assert(removed);
5930af732203SDimitry Andric UNUSED_IF_ASSERT_DISABLED(removed);
5931af732203SDimitry Andric return; // Existing breakpoint removed.
5932af732203SDimitry Andric }
5933af732203SDimitry Andric }
5934af732203SDimitry Andric }
5935af732203SDimitry Andric }
5936af732203SDimitry Andric // No breakpoint found on the location, add it.
5937af732203SDimitry Andric BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint(
5938af732203SDimitry Andric nullptr, // Don't limit the breakpoint to certain modules
5939af732203SDimitry Andric m_file_sp->GetFileSpec(), // Source file
5940af732203SDimitry Andric m_selected_line +
5941af732203SDimitry Andric 1, // Source line number (m_selected_line is zero based)
5942af732203SDimitry Andric 0, // No column specified.
5943af732203SDimitry Andric 0, // No offset
5944af732203SDimitry Andric eLazyBoolCalculate, // Check inlines using global setting
5945af732203SDimitry Andric eLazyBoolCalculate, // Skip prologue using global setting,
5946af732203SDimitry Andric false, // internal
5947af732203SDimitry Andric false, // request_hardware
5948af732203SDimitry Andric eLazyBoolCalculate); // move_to_nearest_code
5949af732203SDimitry Andric } else {
5950af732203SDimitry Andric // Disassembly breakpoint.
5951af732203SDimitry Andric assert(GetNumDisassemblyLines() > 0);
5952af732203SDimitry Andric assert(m_selected_line < GetNumDisassemblyLines());
5953af732203SDimitry Andric const Instruction *inst = m_disassembly_sp->GetInstructionList()
5954af732203SDimitry Andric .GetInstructionAtIndex(m_selected_line)
5955af732203SDimitry Andric .get();
5956af732203SDimitry Andric Address addr = inst->GetAddress();
5957af732203SDimitry Andric // Try to find it.
5958af732203SDimitry Andric BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList();
5959af732203SDimitry Andric const size_t num_bps = bp_list.GetSize();
5960af732203SDimitry Andric for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
5961af732203SDimitry Andric BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
5962af732203SDimitry Andric const size_t num_bps_locs = bp_sp->GetNumLocations();
5963af732203SDimitry Andric for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) {
5964af732203SDimitry Andric BreakpointLocationSP bp_loc_sp =
5965af732203SDimitry Andric bp_sp->GetLocationAtIndex(bp_loc_idx);
5966af732203SDimitry Andric LineEntry bp_loc_line_entry;
5967af732203SDimitry Andric const lldb::addr_t file_addr =
5968af732203SDimitry Andric bp_loc_sp->GetAddress().GetFileAddress();
5969af732203SDimitry Andric if (file_addr == addr.GetFileAddress()) {
5970af732203SDimitry Andric bool removed =
5971af732203SDimitry Andric exe_ctx.GetTargetRef().RemoveBreakpointByID(bp_sp->GetID());
5972af732203SDimitry Andric assert(removed);
5973af732203SDimitry Andric UNUSED_IF_ASSERT_DISABLED(removed);
5974af732203SDimitry Andric return; // Existing breakpoint removed.
5975af732203SDimitry Andric }
5976af732203SDimitry Andric }
5977af732203SDimitry Andric }
5978af732203SDimitry Andric // No breakpoint found on the address, add it.
5979af732203SDimitry Andric BreakpointSP bp_sp =
5980af732203SDimitry Andric exe_ctx.GetTargetRef().CreateBreakpoint(addr, // lldb_private::Address
5981af732203SDimitry Andric false, // internal
5982af732203SDimitry Andric false); // request_hardware
5983af732203SDimitry Andric }
5984af732203SDimitry Andric }
5985af732203SDimitry Andric
5986480093f4SDimitry Andric protected:
5987480093f4SDimitry Andric typedef std::set<uint32_t> BreakpointLines;
5988480093f4SDimitry Andric typedef std::set<lldb::addr_t> BreakpointAddrs;
5989480093f4SDimitry Andric
5990480093f4SDimitry Andric Debugger &m_debugger;
5991480093f4SDimitry Andric SymbolContext m_sc;
5992480093f4SDimitry Andric SourceManager::FileSP m_file_sp;
5993480093f4SDimitry Andric SymbolContextScope *m_disassembly_scope;
5994480093f4SDimitry Andric lldb::DisassemblerSP m_disassembly_sp;
5995480093f4SDimitry Andric AddressRange m_disassembly_range;
5996480093f4SDimitry Andric StreamString m_title;
5997480093f4SDimitry Andric lldb::user_id_t m_tid;
5998480093f4SDimitry Andric int m_line_width;
5999480093f4SDimitry Andric uint32_t m_selected_line; // The selected line
6000480093f4SDimitry Andric uint32_t m_pc_line; // The line with the PC
6001480093f4SDimitry Andric uint32_t m_stop_id;
6002480093f4SDimitry Andric uint32_t m_frame_idx;
6003480093f4SDimitry Andric int m_first_visible_line;
6004af732203SDimitry Andric int m_first_visible_column;
6005480093f4SDimitry Andric int m_min_x;
6006480093f4SDimitry Andric int m_min_y;
6007480093f4SDimitry Andric int m_max_x;
6008480093f4SDimitry Andric int m_max_y;
6009480093f4SDimitry Andric };
6010480093f4SDimitry Andric
6011480093f4SDimitry Andric DisplayOptions ValueObjectListDelegate::g_options = {true};
6012480093f4SDimitry Andric
IOHandlerCursesGUI(Debugger & debugger)6013480093f4SDimitry Andric IOHandlerCursesGUI::IOHandlerCursesGUI(Debugger &debugger)
6014480093f4SDimitry Andric : IOHandler(debugger, IOHandler::Type::Curses) {}
6015480093f4SDimitry Andric
Activate()6016480093f4SDimitry Andric void IOHandlerCursesGUI::Activate() {
6017480093f4SDimitry Andric IOHandler::Activate();
6018480093f4SDimitry Andric if (!m_app_ap) {
60195ffd83dbSDimitry Andric m_app_ap = std::make_unique<Application>(GetInputFILE(), GetOutputFILE());
6020480093f4SDimitry Andric
6021480093f4SDimitry Andric // This is both a window and a menu delegate
6022480093f4SDimitry Andric std::shared_ptr<ApplicationDelegate> app_delegate_sp(
6023480093f4SDimitry Andric new ApplicationDelegate(*m_app_ap, m_debugger));
6024480093f4SDimitry Andric
6025480093f4SDimitry Andric MenuDelegateSP app_menu_delegate_sp =
6026480093f4SDimitry Andric std::static_pointer_cast<MenuDelegate>(app_delegate_sp);
6027480093f4SDimitry Andric MenuSP lldb_menu_sp(
6028480093f4SDimitry Andric new Menu("LLDB", "F1", KEY_F(1), ApplicationDelegate::eMenuID_LLDB));
6029480093f4SDimitry Andric MenuSP exit_menuitem_sp(
6030480093f4SDimitry Andric new Menu("Exit", nullptr, 'x', ApplicationDelegate::eMenuID_LLDBExit));
6031480093f4SDimitry Andric exit_menuitem_sp->SetCannedResult(MenuActionResult::Quit);
6032480093f4SDimitry Andric lldb_menu_sp->AddSubmenu(MenuSP(new Menu(
6033480093f4SDimitry Andric "About LLDB", nullptr, 'a', ApplicationDelegate::eMenuID_LLDBAbout)));
6034480093f4SDimitry Andric lldb_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator)));
6035480093f4SDimitry Andric lldb_menu_sp->AddSubmenu(exit_menuitem_sp);
6036480093f4SDimitry Andric
6037480093f4SDimitry Andric MenuSP target_menu_sp(new Menu("Target", "F2", KEY_F(2),
6038480093f4SDimitry Andric ApplicationDelegate::eMenuID_Target));
6039480093f4SDimitry Andric target_menu_sp->AddSubmenu(MenuSP(new Menu(
6040480093f4SDimitry Andric "Create", nullptr, 'c', ApplicationDelegate::eMenuID_TargetCreate)));
6041480093f4SDimitry Andric target_menu_sp->AddSubmenu(MenuSP(new Menu(
6042480093f4SDimitry Andric "Delete", nullptr, 'd', ApplicationDelegate::eMenuID_TargetDelete)));
6043480093f4SDimitry Andric
6044480093f4SDimitry Andric MenuSP process_menu_sp(new Menu("Process", "F3", KEY_F(3),
6045480093f4SDimitry Andric ApplicationDelegate::eMenuID_Process));
6046480093f4SDimitry Andric process_menu_sp->AddSubmenu(MenuSP(new Menu(
6047480093f4SDimitry Andric "Attach", nullptr, 'a', ApplicationDelegate::eMenuID_ProcessAttach)));
6048af732203SDimitry Andric process_menu_sp->AddSubmenu(
6049af732203SDimitry Andric MenuSP(new Menu("Detach and resume", nullptr, 'd',
6050af732203SDimitry Andric ApplicationDelegate::eMenuID_ProcessDetachResume)));
6051af732203SDimitry Andric process_menu_sp->AddSubmenu(
6052af732203SDimitry Andric MenuSP(new Menu("Detach suspended", nullptr, 's',
6053af732203SDimitry Andric ApplicationDelegate::eMenuID_ProcessDetachSuspended)));
6054480093f4SDimitry Andric process_menu_sp->AddSubmenu(MenuSP(new Menu(
6055480093f4SDimitry Andric "Launch", nullptr, 'l', ApplicationDelegate::eMenuID_ProcessLaunch)));
6056480093f4SDimitry Andric process_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator)));
6057480093f4SDimitry Andric process_menu_sp->AddSubmenu(
6058480093f4SDimitry Andric MenuSP(new Menu("Continue", nullptr, 'c',
6059480093f4SDimitry Andric ApplicationDelegate::eMenuID_ProcessContinue)));
6060480093f4SDimitry Andric process_menu_sp->AddSubmenu(MenuSP(new Menu(
6061480093f4SDimitry Andric "Halt", nullptr, 'h', ApplicationDelegate::eMenuID_ProcessHalt)));
6062480093f4SDimitry Andric process_menu_sp->AddSubmenu(MenuSP(new Menu(
6063480093f4SDimitry Andric "Kill", nullptr, 'k', ApplicationDelegate::eMenuID_ProcessKill)));
6064480093f4SDimitry Andric
6065480093f4SDimitry Andric MenuSP thread_menu_sp(new Menu("Thread", "F4", KEY_F(4),
6066480093f4SDimitry Andric ApplicationDelegate::eMenuID_Thread));
6067480093f4SDimitry Andric thread_menu_sp->AddSubmenu(MenuSP(new Menu(
6068480093f4SDimitry Andric "Step In", nullptr, 'i', ApplicationDelegate::eMenuID_ThreadStepIn)));
6069480093f4SDimitry Andric thread_menu_sp->AddSubmenu(
6070480093f4SDimitry Andric MenuSP(new Menu("Step Over", nullptr, 'v',
6071480093f4SDimitry Andric ApplicationDelegate::eMenuID_ThreadStepOver)));
6072480093f4SDimitry Andric thread_menu_sp->AddSubmenu(MenuSP(new Menu(
6073480093f4SDimitry Andric "Step Out", nullptr, 'o', ApplicationDelegate::eMenuID_ThreadStepOut)));
6074480093f4SDimitry Andric
6075480093f4SDimitry Andric MenuSP view_menu_sp(
6076480093f4SDimitry Andric new Menu("View", "F5", KEY_F(5), ApplicationDelegate::eMenuID_View));
6077480093f4SDimitry Andric view_menu_sp->AddSubmenu(
6078480093f4SDimitry Andric MenuSP(new Menu("Backtrace", nullptr, 'b',
6079480093f4SDimitry Andric ApplicationDelegate::eMenuID_ViewBacktrace)));
6080480093f4SDimitry Andric view_menu_sp->AddSubmenu(
6081480093f4SDimitry Andric MenuSP(new Menu("Registers", nullptr, 'r',
6082480093f4SDimitry Andric ApplicationDelegate::eMenuID_ViewRegisters)));
6083480093f4SDimitry Andric view_menu_sp->AddSubmenu(MenuSP(new Menu(
6084480093f4SDimitry Andric "Source", nullptr, 's', ApplicationDelegate::eMenuID_ViewSource)));
6085480093f4SDimitry Andric view_menu_sp->AddSubmenu(
6086480093f4SDimitry Andric MenuSP(new Menu("Variables", nullptr, 'v',
6087480093f4SDimitry Andric ApplicationDelegate::eMenuID_ViewVariables)));
6088480093f4SDimitry Andric
6089480093f4SDimitry Andric MenuSP help_menu_sp(
6090480093f4SDimitry Andric new Menu("Help", "F6", KEY_F(6), ApplicationDelegate::eMenuID_Help));
6091480093f4SDimitry Andric help_menu_sp->AddSubmenu(MenuSP(new Menu(
6092480093f4SDimitry Andric "GUI Help", nullptr, 'g', ApplicationDelegate::eMenuID_HelpGUIHelp)));
6093480093f4SDimitry Andric
6094480093f4SDimitry Andric m_app_ap->Initialize();
6095480093f4SDimitry Andric WindowSP &main_window_sp = m_app_ap->GetMainWindow();
6096480093f4SDimitry Andric
6097480093f4SDimitry Andric MenuSP menubar_sp(new Menu(Menu::Type::Bar));
6098480093f4SDimitry Andric menubar_sp->AddSubmenu(lldb_menu_sp);
6099480093f4SDimitry Andric menubar_sp->AddSubmenu(target_menu_sp);
6100480093f4SDimitry Andric menubar_sp->AddSubmenu(process_menu_sp);
6101480093f4SDimitry Andric menubar_sp->AddSubmenu(thread_menu_sp);
6102480093f4SDimitry Andric menubar_sp->AddSubmenu(view_menu_sp);
6103480093f4SDimitry Andric menubar_sp->AddSubmenu(help_menu_sp);
6104480093f4SDimitry Andric menubar_sp->SetDelegate(app_menu_delegate_sp);
6105480093f4SDimitry Andric
6106480093f4SDimitry Andric Rect content_bounds = main_window_sp->GetFrame();
6107480093f4SDimitry Andric Rect menubar_bounds = content_bounds.MakeMenuBar();
6108480093f4SDimitry Andric Rect status_bounds = content_bounds.MakeStatusBar();
6109480093f4SDimitry Andric Rect source_bounds;
6110480093f4SDimitry Andric Rect variables_bounds;
6111480093f4SDimitry Andric Rect threads_bounds;
6112480093f4SDimitry Andric Rect source_variables_bounds;
6113480093f4SDimitry Andric content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds,
6114480093f4SDimitry Andric threads_bounds);
6115480093f4SDimitry Andric source_variables_bounds.HorizontalSplitPercentage(0.70, source_bounds,
6116480093f4SDimitry Andric variables_bounds);
6117480093f4SDimitry Andric
6118480093f4SDimitry Andric WindowSP menubar_window_sp =
6119480093f4SDimitry Andric main_window_sp->CreateSubWindow("Menubar", menubar_bounds, false);
6120480093f4SDimitry Andric // Let the menubar get keys if the active window doesn't handle the keys
6121480093f4SDimitry Andric // that are typed so it can respond to menubar key presses.
6122480093f4SDimitry Andric menubar_window_sp->SetCanBeActive(
6123480093f4SDimitry Andric false); // Don't let the menubar become the active window
6124480093f4SDimitry Andric menubar_window_sp->SetDelegate(menubar_sp);
6125480093f4SDimitry Andric
6126480093f4SDimitry Andric WindowSP source_window_sp(
6127480093f4SDimitry Andric main_window_sp->CreateSubWindow("Source", source_bounds, true));
6128480093f4SDimitry Andric WindowSP variables_window_sp(
6129480093f4SDimitry Andric main_window_sp->CreateSubWindow("Variables", variables_bounds, false));
6130480093f4SDimitry Andric WindowSP threads_window_sp(
6131480093f4SDimitry Andric main_window_sp->CreateSubWindow("Threads", threads_bounds, false));
6132480093f4SDimitry Andric WindowSP status_window_sp(
6133480093f4SDimitry Andric main_window_sp->CreateSubWindow("Status", status_bounds, false));
6134480093f4SDimitry Andric status_window_sp->SetCanBeActive(
6135480093f4SDimitry Andric false); // Don't let the status bar become the active window
6136480093f4SDimitry Andric main_window_sp->SetDelegate(
6137480093f4SDimitry Andric std::static_pointer_cast<WindowDelegate>(app_delegate_sp));
6138480093f4SDimitry Andric source_window_sp->SetDelegate(
6139480093f4SDimitry Andric WindowDelegateSP(new SourceFileWindowDelegate(m_debugger)));
6140480093f4SDimitry Andric variables_window_sp->SetDelegate(
6141480093f4SDimitry Andric WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger)));
6142480093f4SDimitry Andric TreeDelegateSP thread_delegate_sp(new ThreadsTreeDelegate(m_debugger));
6143480093f4SDimitry Andric threads_window_sp->SetDelegate(WindowDelegateSP(
6144480093f4SDimitry Andric new TreeWindowDelegate(m_debugger, thread_delegate_sp)));
6145480093f4SDimitry Andric status_window_sp->SetDelegate(
6146480093f4SDimitry Andric WindowDelegateSP(new StatusBarWindowDelegate(m_debugger)));
6147480093f4SDimitry Andric
6148480093f4SDimitry Andric // Show the main help window once the first time the curses GUI is launched
6149480093f4SDimitry Andric static bool g_showed_help = false;
6150480093f4SDimitry Andric if (!g_showed_help) {
6151480093f4SDimitry Andric g_showed_help = true;
6152480093f4SDimitry Andric main_window_sp->CreateHelpSubwindow();
6153480093f4SDimitry Andric }
6154480093f4SDimitry Andric
6155af732203SDimitry Andric // All colors with black background.
6156af732203SDimitry Andric init_pair(1, COLOR_BLACK, COLOR_BLACK);
6157af732203SDimitry Andric init_pair(2, COLOR_RED, COLOR_BLACK);
6158af732203SDimitry Andric init_pair(3, COLOR_GREEN, COLOR_BLACK);
6159af732203SDimitry Andric init_pair(4, COLOR_YELLOW, COLOR_BLACK);
6160af732203SDimitry Andric init_pair(5, COLOR_BLUE, COLOR_BLACK);
6161af732203SDimitry Andric init_pair(6, COLOR_MAGENTA, COLOR_BLACK);
6162af732203SDimitry Andric init_pair(7, COLOR_CYAN, COLOR_BLACK);
6163af732203SDimitry Andric init_pair(8, COLOR_WHITE, COLOR_BLACK);
6164af732203SDimitry Andric // All colors with blue background.
6165af732203SDimitry Andric init_pair(9, COLOR_BLACK, COLOR_BLUE);
6166af732203SDimitry Andric init_pair(10, COLOR_RED, COLOR_BLUE);
6167af732203SDimitry Andric init_pair(11, COLOR_GREEN, COLOR_BLUE);
6168af732203SDimitry Andric init_pair(12, COLOR_YELLOW, COLOR_BLUE);
6169af732203SDimitry Andric init_pair(13, COLOR_BLUE, COLOR_BLUE);
6170af732203SDimitry Andric init_pair(14, COLOR_MAGENTA, COLOR_BLUE);
6171af732203SDimitry Andric init_pair(15, COLOR_CYAN, COLOR_BLUE);
6172af732203SDimitry Andric init_pair(16, COLOR_WHITE, COLOR_BLUE);
6173af732203SDimitry Andric // These must match the order in the color indexes enum.
6174af732203SDimitry Andric init_pair(17, COLOR_BLACK, COLOR_WHITE);
6175af732203SDimitry Andric init_pair(18, COLOR_MAGENTA, COLOR_WHITE);
6176af732203SDimitry Andric static_assert(LastColorPairIndex == 18, "Color indexes do not match.");
6177*5f7ddb14SDimitry Andric
6178*5f7ddb14SDimitry Andric define_key("\033[Z", KEY_SHIFT_TAB);
6179480093f4SDimitry Andric }
6180480093f4SDimitry Andric }
6181480093f4SDimitry Andric
Deactivate()6182480093f4SDimitry Andric void IOHandlerCursesGUI::Deactivate() { m_app_ap->Terminate(); }
6183480093f4SDimitry Andric
Run()6184480093f4SDimitry Andric void IOHandlerCursesGUI::Run() {
6185480093f4SDimitry Andric m_app_ap->Run(m_debugger);
6186480093f4SDimitry Andric SetIsDone(true);
6187480093f4SDimitry Andric }
6188480093f4SDimitry Andric
6189480093f4SDimitry Andric IOHandlerCursesGUI::~IOHandlerCursesGUI() = default;
6190480093f4SDimitry Andric
Cancel()6191480093f4SDimitry Andric void IOHandlerCursesGUI::Cancel() {}
6192480093f4SDimitry Andric
Interrupt()6193480093f4SDimitry Andric bool IOHandlerCursesGUI::Interrupt() { return false; }
6194480093f4SDimitry Andric
GotEOF()6195480093f4SDimitry Andric void IOHandlerCursesGUI::GotEOF() {}
6196480093f4SDimitry Andric
TerminalSizeChanged()6197af732203SDimitry Andric void IOHandlerCursesGUI::TerminalSizeChanged() {
6198af732203SDimitry Andric m_app_ap->TerminalSizeChanged();
6199af732203SDimitry Andric }
6200af732203SDimitry Andric
6201480093f4SDimitry Andric #endif // LLDB_ENABLE_CURSES
6202