Coverage Report

Created: 2022-01-18 06:27

/Users/buildslave/jenkins/workspace/coverage/llvm-project/lldb/source/Core/IOHandlerCursesGUI.cpp
Line
Count
Source (jump to first uncovered line)
1
//===-- IOHandlerCursesGUI.cpp --------------------------------------------===//
2
//
3
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4
// See https://llvm.org/LICENSE.txt for license information.
5
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6
//
7
//===----------------------------------------------------------------------===//
8
9
#include "lldb/Core/IOHandlerCursesGUI.h"
10
#include "lldb/Host/Config.h"
11
12
#if LLDB_ENABLE_CURSES
13
#if CURSES_HAVE_NCURSES_CURSES_H
14
#include <ncurses/curses.h>
15
#include <ncurses/panel.h>
16
#else
17
#include <curses.h>
18
#include <panel.h>
19
#endif
20
#endif
21
22
#if defined(__APPLE__)
23
#include <deque>
24
#endif
25
#include <string>
26
27
#include "lldb/Core/Debugger.h"
28
#include "lldb/Core/StreamFile.h"
29
#include "lldb/Core/ValueObjectUpdater.h"
30
#include "lldb/Host/File.h"
31
#include "lldb/Utility/Predicate.h"
32
#include "lldb/Utility/Status.h"
33
#include "lldb/Utility/StreamString.h"
34
#include "lldb/Utility/StringList.h"
35
#include "lldb/lldb-forward.h"
36
37
#include "lldb/Interpreter/CommandCompletions.h"
38
#include "lldb/Interpreter/CommandInterpreter.h"
39
#include "lldb/Interpreter/OptionGroupPlatform.h"
40
41
#if LLDB_ENABLE_CURSES
42
#include "lldb/Breakpoint/BreakpointLocation.h"
43
#include "lldb/Core/Module.h"
44
#include "lldb/Core/PluginManager.h"
45
#include "lldb/Core/ValueObject.h"
46
#include "lldb/Core/ValueObjectRegister.h"
47
#include "lldb/Symbol/Block.h"
48
#include "lldb/Symbol/CompileUnit.h"
49
#include "lldb/Symbol/Function.h"
50
#include "lldb/Symbol/Symbol.h"
51
#include "lldb/Symbol/VariableList.h"
52
#include "lldb/Target/Process.h"
53
#include "lldb/Target/RegisterContext.h"
54
#include "lldb/Target/StackFrame.h"
55
#include "lldb/Target/StopInfo.h"
56
#include "lldb/Target/Target.h"
57
#include "lldb/Target/Thread.h"
58
#include "lldb/Utility/State.h"
59
#endif
60
61
#include "llvm/ADT/StringRef.h"
62
63
#ifdef _WIN32
64
#include "lldb/Host/windows/windows.h"
65
#endif
66
67
#include <memory>
68
#include <mutex>
69
70
#include <cassert>
71
#include <cctype>
72
#include <cerrno>
73
#include <cstdint>
74
#include <cstdio>
75
#include <cstring>
76
#include <functional>
77
#include <type_traits>
78
79
using namespace lldb;
80
using namespace lldb_private;
81
using llvm::StringRef;
82
83
// we may want curses to be disabled for some builds for instance, windows
84
#if LLDB_ENABLE_CURSES
85
86
0
#define KEY_CTRL_A 1
87
0
#define KEY_CTRL_E 5
88
0
#define KEY_CTRL_K 11
89
0
#define KEY_RETURN 10
90
0
#define KEY_ESCAPE 27
91
0
#define KEY_DELETE 127
92
93
0
#define KEY_SHIFT_TAB (KEY_MAX + 1)
94
0
#define KEY_ALT_ENTER (KEY_MAX + 2)
95
96
namespace curses {
97
class Menu;
98
class MenuDelegate;
99
class Window;
100
class WindowDelegate;
101
typedef std::shared_ptr<Menu> MenuSP;
102
typedef std::shared_ptr<MenuDelegate> MenuDelegateSP;
103
typedef std::shared_ptr<Window> WindowSP;
104
typedef std::shared_ptr<WindowDelegate> WindowDelegateSP;
105
typedef std::vector<MenuSP> Menus;
106
typedef std::vector<WindowSP> Windows;
107
typedef std::vector<WindowDelegateSP> WindowDelegates;
108
109
#if 0
110
type summary add -s "x=${var.x}, y=${var.y}" curses::Point
111
type summary add -s "w=${var.width}, h=${var.height}" curses::Size
112
type summary add -s "${var.origin%S} ${var.size%S}" curses::Rect
113
#endif
114
115
struct Point {
116
  int x;
117
  int y;
118
119
0
  Point(int _x = 0, int _y = 0) : x(_x), y(_y) {}
120
121
0
  void Clear() {
122
0
    x = 0;
123
0
    y = 0;
124
0
  }
125
126
0
  Point &operator+=(const Point &rhs) {
127
0
    x += rhs.x;
128
0
    y += rhs.y;
129
0
    return *this;
130
0
  }
131
132
0
  void Dump() { printf("(x=%i, y=%i)\n", x, y); }
133
};
134
135
0
bool operator==(const Point &lhs, const Point &rhs) {
136
0
  return lhs.x == rhs.x && lhs.y == rhs.y;
137
0
}
138
139
0
bool operator!=(const Point &lhs, const Point &rhs) {
140
0
  return lhs.x != rhs.x || lhs.y != rhs.y;
141
0
}
142
143
struct Size {
144
  int width;
145
  int height;
146
0
  Size(int w = 0, int h = 0) : width(w), height(h) {}
147
148
0
  void Clear() {
149
0
    width = 0;
150
0
    height = 0;
151
0
  }
152
153
0
  void Dump() { printf("(w=%i, h=%i)\n", width, height); }
154
};
155
156
0
bool operator==(const Size &lhs, const Size &rhs) {
157
0
  return lhs.width == rhs.width && lhs.height == rhs.height;
158
0
}
159
160
0
bool operator!=(const Size &lhs, const Size &rhs) {
161
0
  return lhs.width != rhs.width || lhs.height != rhs.height;
162
0
}
163
164
struct Rect {
165
  Point origin;
166
  Size size;
167
168
0
  Rect() : origin(), size() {}
169
170
0
  Rect(const Point &p, const Size &s) : origin(p), size(s) {}
171
172
0
  void Clear() {
173
0
    origin.Clear();
174
0
    size.Clear();
175
0
  }
176
177
0
  void Dump() {
178
0
    printf("(x=%i, y=%i), w=%i, h=%i)\n", origin.x, origin.y, size.width,
179
0
           size.height);
180
0
  }
181
182
0
  void Inset(int w, int h) {
183
0
    if (size.width > w * 2)
184
0
      size.width -= w * 2;
185
0
    origin.x += w;
186
187
0
    if (size.height > h * 2)
188
0
      size.height -= h * 2;
189
0
    origin.y += h;
190
0
  }
191
192
  // Return a status bar rectangle which is the last line of this rectangle.
193
  // This rectangle will be modified to not include the status bar area.
194
0
  Rect MakeStatusBar() {
195
0
    Rect status_bar;
196
0
    if (size.height > 1) {
197
0
      status_bar.origin.x = origin.x;
198
0
      status_bar.origin.y = size.height;
199
0
      status_bar.size.width = size.width;
200
0
      status_bar.size.height = 1;
201
0
      --size.height;
202
0
    }
203
0
    return status_bar;
204
0
  }
205
206
  // Return a menubar rectangle which is the first line of this rectangle. This
207
  // rectangle will be modified to not include the menubar area.
208
0
  Rect MakeMenuBar() {
209
0
    Rect menubar;
210
0
    if (size.height > 1) {
211
0
      menubar.origin.x = origin.x;
212
0
      menubar.origin.y = origin.y;
213
0
      menubar.size.width = size.width;
214
0
      menubar.size.height = 1;
215
0
      ++origin.y;
216
0
      --size.height;
217
0
    }
218
0
    return menubar;
219
0
  }
220
221
  void HorizontalSplitPercentage(float top_percentage, Rect &top,
222
0
                                 Rect &bottom) const {
223
0
    float top_height = top_percentage * size.height;
224
0
    HorizontalSplit(top_height, top, bottom);
225
0
  }
226
227
0
  void HorizontalSplit(int top_height, Rect &top, Rect &bottom) const {
228
0
    top = *this;
229
0
    if (top_height < size.height) {
230
0
      top.size.height = top_height;
231
0
      bottom.origin.x = origin.x;
232
0
      bottom.origin.y = origin.y + top.size.height;
233
0
      bottom.size.width = size.width;
234
0
      bottom.size.height = size.height - top.size.height;
235
0
    } else {
236
0
      bottom.Clear();
237
0
    }
238
0
  }
239
240
  void VerticalSplitPercentage(float left_percentage, Rect &left,
241
0
                               Rect &right) const {
242
0
    float left_width = left_percentage * size.width;
243
0
    VerticalSplit(left_width, left, right);
244
0
  }
245
246
0
  void VerticalSplit(int left_width, Rect &left, Rect &right) const {
247
0
    left = *this;
248
0
    if (left_width < size.width) {
249
0
      left.size.width = left_width;
250
0
      right.origin.x = origin.x + left.size.width;
251
0
      right.origin.y = origin.y;
252
0
      right.size.width = size.width - left.size.width;
253
0
      right.size.height = size.height;
254
0
    } else {
255
0
      right.Clear();
256
0
    }
257
0
  }
258
};
259
260
0
bool operator==(const Rect &lhs, const Rect &rhs) {
261
0
  return lhs.origin == rhs.origin && lhs.size == rhs.size;
262
0
}
263
264
0
bool operator!=(const Rect &lhs, const Rect &rhs) {
265
0
  return lhs.origin != rhs.origin || lhs.size != rhs.size;
266
0
}
267
268
enum HandleCharResult {
269
  eKeyNotHandled = 0,
270
  eKeyHandled = 1,
271
  eQuitApplication = 2
272
};
273
274
enum class MenuActionResult {
275
  Handled,
276
  NotHandled,
277
  Quit // Exit all menus and quit
278
};
279
280
struct KeyHelp {
281
  int ch;
282
  const char *description;
283
};
284
285
// COLOR_PAIR index names
286
enum {
287
  // First 16 colors are 8 black background and 8 blue background colors,
288
  // needed by OutputColoredStringTruncated().
289
  BlackOnBlack = 1,
290
  RedOnBlack,
291
  GreenOnBlack,
292
  YellowOnBlack,
293
  BlueOnBlack,
294
  MagentaOnBlack,
295
  CyanOnBlack,
296
  WhiteOnBlack,
297
  BlackOnBlue,
298
  RedOnBlue,
299
  GreenOnBlue,
300
  YellowOnBlue,
301
  BlueOnBlue,
302
  MagentaOnBlue,
303
  CyanOnBlue,
304
  WhiteOnBlue,
305
  // Other colors, as needed.
306
  BlackOnWhite,
307
  MagentaOnWhite,
308
  LastColorPairIndex = MagentaOnWhite
309
};
310
311
class WindowDelegate {
312
public:
313
0
  virtual ~WindowDelegate() = default;
314
315
0
  virtual bool WindowDelegateDraw(Window &window, bool force) {
316
0
    return false; // Drawing not handled
317
0
  }
318
319
0
  virtual HandleCharResult WindowDelegateHandleChar(Window &window, int key) {
320
0
    return eKeyNotHandled;
321
0
  }
322
323
0
  virtual const char *WindowDelegateGetHelpText() { return nullptr; }
324
325
0
  virtual KeyHelp *WindowDelegateGetKeyHelp() { return nullptr; }
326
};
327
328
class HelpDialogDelegate : public WindowDelegate {
329
public:
330
  HelpDialogDelegate(const char *text, KeyHelp *key_help_array);
331
332
  ~HelpDialogDelegate() override;
333
334
  bool WindowDelegateDraw(Window &window, bool force) override;
335
336
  HandleCharResult WindowDelegateHandleChar(Window &window, int key) override;
337
338
0
  size_t GetNumLines() const { return m_text.GetSize(); }
339
340
0
  size_t GetMaxLineLength() const { return m_text.GetMaxStringLength(); }
341
342
protected:
343
  StringList m_text;
344
  int m_first_visible_line;
345
};
346
347
// A surface is an abstraction for something than can be drawn on. The surface
348
// have a width, a height, a cursor position, and a multitude of drawing
349
// operations. This type should be sub-classed to get an actually useful ncurses
350
// object, such as a Window or a Pad.
351
class Surface {
352
public:
353
  enum class Type { Window, Pad };
354
355
0
  Surface(Surface::Type type) : m_type(type), m_window(nullptr) {}
356
357
0
  WINDOW *get() { return m_window; }
358
359
0
  operator WINDOW *() { return m_window; }
360
361
0
  Surface SubSurface(Rect bounds) {
362
0
    Surface subSurface(m_type);
363
0
    if (m_type == Type::Pad)
364
0
      subSurface.m_window =
365
0
          ::subpad(m_window, bounds.size.height, bounds.size.width,
366
0
                   bounds.origin.y, bounds.origin.x);
367
0
    else
368
0
      subSurface.m_window =
369
0
          ::derwin(m_window, bounds.size.height, bounds.size.width,
370
0
                   bounds.origin.y, bounds.origin.x);
371
0
    return subSurface;
372
0
  }
373
374
  // Copy a region of the surface to another surface.
375
  void CopyToSurface(Surface &target, Point source_origin, Point target_origin,
376
0
                     Size size) {
377
0
    ::copywin(m_window, target.get(), source_origin.y, source_origin.x,
378
0
              target_origin.y, target_origin.x,
379
0
              target_origin.y + size.height - 1,
380
0
              target_origin.x + size.width - 1, false);
381
0
  }
382
383
0
  int GetCursorX() const { return getcurx(m_window); }
384
0
  int GetCursorY() const { return getcury(m_window); }
385
0
  void MoveCursor(int x, int y) { ::wmove(m_window, y, x); }
386
387
0
  void AttributeOn(attr_t attr) { ::wattron(m_window, attr); }
388
0
  void AttributeOff(attr_t attr) { ::wattroff(m_window, attr); }
389
390
0
  int GetMaxX() const { return getmaxx(m_window); }
391
0
  int GetMaxY() const { return getmaxy(m_window); }
392
0
  int GetWidth() const { return GetMaxX(); }
393
0
  int GetHeight() const { return GetMaxY(); }
394
0
  Size GetSize() const { return Size(GetWidth(), GetHeight()); }
395
  // Get a zero origin rectangle width the surface size.
396
0
  Rect GetFrame() const { return Rect(Point(), GetSize()); }
397
398
0
  void Clear() { ::wclear(m_window); }
399
0
  void Erase() { ::werase(m_window); }
400
401
0
  void SetBackground(int color_pair_idx) {
402
0
    ::wbkgd(m_window, COLOR_PAIR(color_pair_idx));
403
0
  }
404
405
0
  void PutChar(int ch) { ::waddch(m_window, ch); }
406
0
  void PutCString(const char *s, int len = -1) { ::waddnstr(m_window, s, len); }
407
408
0
  void PutCStringTruncated(int right_pad, const char *s, int len = -1) {
409
0
    int bytes_left = GetWidth() - GetCursorX();
410
0
    if (bytes_left > right_pad) {
411
0
      bytes_left -= right_pad;
412
0
      ::waddnstr(m_window, s, len < 0 ? bytes_left : std::min(bytes_left, len));
413
0
    }
414
0
  }
415
416
0
  void Printf(const char *format, ...) __attribute__((format(printf, 2, 3))) {
417
0
    va_list args;
418
0
    va_start(args, format);
419
0
    vw_printw(m_window, format, args);
420
0
    va_end(args);
421
0
  }
422
423
  void PrintfTruncated(int right_pad, const char *format, ...)
424
0
      __attribute__((format(printf, 3, 4))) {
425
0
    va_list args;
426
0
    va_start(args, format);
427
0
    StreamString strm;
428
0
    strm.PrintfVarArg(format, args);
429
0
    va_end(args);
430
0
    PutCStringTruncated(right_pad, strm.GetData());
431
0
  }
432
433
0
  void VerticalLine(int n, chtype v_char = ACS_VLINE) {
434
0
    ::wvline(m_window, v_char, n);
435
0
  }
436
0
  void HorizontalLine(int n, chtype h_char = ACS_HLINE) {
437
0
    ::whline(m_window, h_char, n);
438
0
  }
439
0
  void Box(chtype v_char = ACS_VLINE, chtype h_char = ACS_HLINE) {
440
0
    ::box(m_window, v_char, h_char);
441
0
  }
442
443
  void TitledBox(const char *title, chtype v_char = ACS_VLINE,
444
0
                 chtype h_char = ACS_HLINE) {
445
0
    Box(v_char, h_char);
446
0
    int title_offset = 2;
447
0
    MoveCursor(title_offset, 0);
448
0
    PutChar('[');
449
0
    PutCString(title, GetWidth() - title_offset);
450
0
    PutChar(']');
451
0
  }
452
453
  void Box(const Rect &bounds, chtype v_char = ACS_VLINE,
454
0
           chtype h_char = ACS_HLINE) {
455
0
    MoveCursor(bounds.origin.x, bounds.origin.y);
456
0
    VerticalLine(bounds.size.height);
457
0
    HorizontalLine(bounds.size.width);
458
0
    PutChar(ACS_ULCORNER);
459
0
460
0
    MoveCursor(bounds.origin.x + bounds.size.width - 1, bounds.origin.y);
461
0
    VerticalLine(bounds.size.height);
462
0
    PutChar(ACS_URCORNER);
463
0
464
0
    MoveCursor(bounds.origin.x, bounds.origin.y + bounds.size.height - 1);
465
0
    HorizontalLine(bounds.size.width);
466
0
    PutChar(ACS_LLCORNER);
467
0
468
0
    MoveCursor(bounds.origin.x + bounds.size.width - 1,
469
0
               bounds.origin.y + bounds.size.height - 1);
470
0
    PutChar(ACS_LRCORNER);
471
0
  }
472
473
  void TitledBox(const Rect &bounds, const char *title,
474
0
                 chtype v_char = ACS_VLINE, chtype h_char = ACS_HLINE) {
475
0
    Box(bounds, v_char, h_char);
476
0
    int title_offset = 2;
477
0
    MoveCursor(bounds.origin.x + title_offset, bounds.origin.y);
478
0
    PutChar('[');
479
0
    PutCString(title, bounds.size.width - title_offset);
480
0
    PutChar(']');
481
0
  }
482
483
  // Curses doesn't allow direct output of color escape sequences, but that's
484
  // how we get source lines from the Highligher class. Read the line and
485
  // convert color escape sequences to curses color attributes. Use
486
  // first_skip_count to skip leading visible characters. Returns false if all
487
  // visible characters were skipped due to first_skip_count.
488
  bool OutputColoredStringTruncated(int right_pad, StringRef string,
489
                                    size_t skip_first_count,
490
0
                                    bool use_blue_background) {
491
0
    attr_t saved_attr;
492
0
    short saved_pair;
493
0
    bool result = false;
494
0
    wattr_get(m_window, &saved_attr, &saved_pair, nullptr);
495
0
    if (use_blue_background)
496
0
      ::wattron(m_window, COLOR_PAIR(WhiteOnBlue));
497
0
    while (!string.empty()) {
498
0
      size_t esc_pos = string.find('\x1b');
499
0
      if (esc_pos == StringRef::npos) {
500
0
        string = string.substr(skip_first_count);
501
0
        if (!string.empty()) {
502
0
          PutCStringTruncated(right_pad, string.data(), string.size());
503
0
          result = true;
504
0
        }
505
0
        break;
506
0
      }
507
0
      if (esc_pos > 0) {
508
0
        if (skip_first_count > 0) {
509
0
          int skip = std::min(esc_pos, skip_first_count);
510
0
          string = string.substr(skip);
511
0
          skip_first_count -= skip;
512
0
          esc_pos -= skip;
513
0
        }
514
0
        if (esc_pos > 0) {
515
0
          PutCStringTruncated(right_pad, string.data(), esc_pos);
516
0
          result = true;
517
0
          string = string.drop_front(esc_pos);
518
0
        }
519
0
      }
520
0
      bool consumed = string.consume_front("\x1b");
521
0
      assert(consumed);
522
0
      UNUSED_IF_ASSERT_DISABLED(consumed);
523
      // This is written to match our Highlighter classes, which seem to
524
      // generate only foreground color escape sequences. If necessary, this
525
      // will need to be extended.
526
0
      if (!string.consume_front("[")) {
527
0
        llvm::errs() << "Missing '[' in color escape sequence.\n";
528
0
        continue;
529
0
      }
530
      // Only 8 basic foreground colors and reset, our Highlighter doesn't use
531
      // anything else.
532
0
      int value;
533
0
      if (!!string.consumeInteger(10, value) || // Returns false on success.
534
0
          !(value == 0 || (value >= 30 && value <= 37))) {
535
0
        llvm::errs() << "No valid color code in color escape sequence.\n";
536
0
        continue;
537
0
      }
538
0
      if (!string.consume_front("m")) {
539
0
        llvm::errs() << "Missing 'm' in color escape sequence.\n";
540
0
        continue;
541
0
      }
542
0
      if (value == 0) { // Reset.
543
0
        wattr_set(m_window, saved_attr, saved_pair, nullptr);
544
0
        if (use_blue_background)
545
0
          ::wattron(m_window, COLOR_PAIR(WhiteOnBlue));
546
0
      } else {
547
        // Mapped directly to first 16 color pairs (black/blue background).
548
0
        ::wattron(m_window,
549
0
                  COLOR_PAIR(value - 30 + 1 + (use_blue_background ? 8 : 0)));
550
0
      }
551
0
    }
552
0
    wattr_set(m_window, saved_attr, saved_pair, nullptr);
553
0
    return result;
554
0
  }
555
556
protected:
557
  Type m_type;
558
  WINDOW *m_window;
559
};
560
561
class Pad : public Surface {
562
public:
563
0
  Pad(Size size) : Surface(Surface::Type::Pad) {
564
0
    m_window = ::newpad(size.height, size.width);
565
0
  }
566
567
0
  ~Pad() { ::delwin(m_window); }
568
};
569
570
class Window : public Surface {
571
public:
572
  Window(const char *name)
573
      : Surface(Surface::Type::Window), m_name(name), m_panel(nullptr),
574
        m_parent(nullptr), m_subwindows(), m_delegate_sp(),
575
        m_curr_active_window_idx(UINT32_MAX),
576
        m_prev_active_window_idx(UINT32_MAX), m_delete(false),
577
0
        m_needs_update(true), m_can_activate(true), m_is_subwin(false) {}
578
579
  Window(const char *name, WINDOW *w, bool del = true)
580
      : Surface(Surface::Type::Window), m_name(name), m_panel(nullptr),
581
        m_parent(nullptr), m_subwindows(), m_delegate_sp(),
582
        m_curr_active_window_idx(UINT32_MAX),
583
        m_prev_active_window_idx(UINT32_MAX), m_delete(del),
584
0
        m_needs_update(true), m_can_activate(true), m_is_subwin(false) {
585
0
    if (w)
586
0
      Reset(w);
587
0
  }
588
589
  Window(const char *name, const Rect &bounds)
590
      : Surface(Surface::Type::Window), m_name(name), m_parent(nullptr),
591
        m_subwindows(), m_delegate_sp(), m_curr_active_window_idx(UINT32_MAX),
592
        m_prev_active_window_idx(UINT32_MAX), m_delete(true),
593
0
        m_needs_update(true), m_can_activate(true), m_is_subwin(false) {
594
0
    Reset(::newwin(bounds.size.height, bounds.size.width, bounds.origin.y,
595
0
                   bounds.origin.y));
596
0
  }
597
598
0
  virtual ~Window() {
599
0
    RemoveSubWindows();
600
0
    Reset();
601
0
  }
602
603
0
  void Reset(WINDOW *w = nullptr, bool del = true) {
604
0
    if (m_window == w)
605
0
      return;
606
607
0
    if (m_panel) {
608
0
      ::del_panel(m_panel);
609
0
      m_panel = nullptr;
610
0
    }
611
0
    if (m_window && m_delete) {
612
0
      ::delwin(m_window);
613
0
      m_window = nullptr;
614
0
      m_delete = false;
615
0
    }
616
0
    if (w) {
617
0
      m_window = w;
618
0
      m_panel = ::new_panel(m_window);
619
0
      m_delete = del;
620
0
    }
621
0
  }
622
623
  // Get the rectangle in our parent window
624
0
  Rect GetBounds() const { return Rect(GetParentOrigin(), GetSize()); }
625
626
0
  Rect GetCenteredRect(int width, int height) {
627
0
    Size size = GetSize();
628
0
    width = std::min(size.width, width);
629
0
    height = std::min(size.height, height);
630
0
    int x = (size.width - width) / 2;
631
0
    int y = (size.height - height) / 2;
632
0
    return Rect(Point(x, y), Size(width, height));
633
0
  }
634
635
0
  int GetChar() { return ::wgetch(m_window); }
636
0
  Point GetParentOrigin() const { return Point(GetParentX(), GetParentY()); }
637
0
  int GetParentX() const { return getparx(m_window); }
638
0
  int GetParentY() const { return getpary(m_window); }
639
0
  void MoveWindow(int x, int y) { MoveWindow(Point(x, y)); }
640
0
  void Resize(int w, int h) { ::wresize(m_window, h, w); }
641
0
  void Resize(const Size &size) {
642
0
    ::wresize(m_window, size.height, size.width);
643
0
  }
644
0
  void MoveWindow(const Point &origin) {
645
0
    const bool moving_window = origin != GetParentOrigin();
646
0
    if (m_is_subwin && moving_window) {
647
      // Can't move subwindows, must delete and re-create
648
0
      Size size = GetSize();
649
0
      Reset(::subwin(m_parent->m_window, size.height, size.width, origin.y,
650
0
                     origin.x),
651
0
            true);
652
0
    } else {
653
0
      ::mvwin(m_window, origin.y, origin.x);
654
0
    }
655
0
  }
656
657
0
  void SetBounds(const Rect &bounds) {
658
0
    const bool moving_window = bounds.origin != GetParentOrigin();
659
0
    if (m_is_subwin && moving_window) {
660
      // Can't move subwindows, must delete and re-create
661
0
      Reset(::subwin(m_parent->m_window, bounds.size.height, bounds.size.width,
662
0
                     bounds.origin.y, bounds.origin.x),
663
0
            true);
664
0
    } else {
665
0
      if (moving_window)
666
0
        MoveWindow(bounds.origin);
667
0
      Resize(bounds.size);
668
0
    }
669
0
  }
670
671
0
  void Touch() {
672
0
    ::touchwin(m_window);
673
0
    if (m_parent)
674
0
      m_parent->Touch();
675
0
  }
676
677
  WindowSP CreateSubWindow(const char *name, const Rect &bounds,
678
0
                           bool make_active) {
679
0
    auto get_window = [this, &bounds]() {
680
0
      return m_window
681
0
                 ? ::subwin(m_window, bounds.size.height, bounds.size.width,
682
0
                            bounds.origin.y, bounds.origin.x)
683
0
                 : ::newwin(bounds.size.height, bounds.size.width,
684
0
                            bounds.origin.y, bounds.origin.x);
685
0
    };
686
0
    WindowSP subwindow_sp = std::make_shared<Window>(name, get_window(), true);
687
0
    subwindow_sp->m_is_subwin = subwindow_sp.operator bool();
688
0
    subwindow_sp->m_parent = this;
689
0
    if (make_active) {
690
0
      m_prev_active_window_idx = m_curr_active_window_idx;
691
0
      m_curr_active_window_idx = m_subwindows.size();
692
0
    }
693
0
    m_subwindows.push_back(subwindow_sp);
694
0
    ::top_panel(subwindow_sp->m_panel);
695
0
    m_needs_update = true;
696
0
    return subwindow_sp;
697
0
  }
698
699
0
  bool RemoveSubWindow(Window *window) {
700
0
    Windows::iterator pos, end = m_subwindows.end();
701
0
    size_t i = 0;
702
0
    for (pos = m_subwindows.begin(); pos != end; ++pos, ++i) {
703
0
      if ((*pos).get() == window) {
704
0
        if (m_prev_active_window_idx == i)
705
0
          m_prev_active_window_idx = UINT32_MAX;
706
0
        else if (m_prev_active_window_idx != UINT32_MAX &&
707
0
                 m_prev_active_window_idx > i)
708
0
          --m_prev_active_window_idx;
709
710
0
        if (m_curr_active_window_idx == i)
711
0
          m_curr_active_window_idx = UINT32_MAX;
712
0
        else if (m_curr_active_window_idx != UINT32_MAX &&
713
0
                 m_curr_active_window_idx > i)
714
0
          --m_curr_active_window_idx;
715
0
        window->Erase();
716
0
        m_subwindows.erase(pos);
717
0
        m_needs_update = true;
718
0
        if (m_parent)
719
0
          m_parent->Touch();
720
0
        else
721
0
          ::touchwin(stdscr);
722
0
        return true;
723
0
      }
724
0
    }
725
0
    return false;
726
0
  }
727
728
0
  WindowSP FindSubWindow(const char *name) {
729
0
    Windows::iterator pos, end = m_subwindows.end();
730
0
    size_t i = 0;
731
0
    for (pos = m_subwindows.begin(); pos != end; ++pos, ++i) {
732
0
      if ((*pos)->m_name == name)
733
0
        return *pos;
734
0
    }
735
0
    return WindowSP();
736
0
  }
737
738
0
  void RemoveSubWindows() {
739
0
    m_curr_active_window_idx = UINT32_MAX;
740
0
    m_prev_active_window_idx = UINT32_MAX;
741
0
    for (Windows::iterator pos = m_subwindows.begin();
742
0
         pos != m_subwindows.end(); pos = m_subwindows.erase(pos)) {
743
0
      (*pos)->Erase();
744
0
    }
745
0
    if (m_parent)
746
0
      m_parent->Touch();
747
0
    else
748
0
      ::touchwin(stdscr);
749
0
  }
750
751
  // Window drawing utilities
752
0
  void DrawTitleBox(const char *title, const char *bottom_message = nullptr) {
753
0
    attr_t attr = 0;
754
0
    if (IsActive())
755
0
      attr = A_BOLD | COLOR_PAIR(BlackOnWhite);
756
0
    else
757
0
      attr = 0;
758
0
    if (attr)
759
0
      AttributeOn(attr);
760
761
0
    Box();
762
0
    MoveCursor(3, 0);
763
764
0
    if (title && title[0]) {
765
0
      PutChar('<');
766
0
      PutCString(title);
767
0
      PutChar('>');
768
0
    }
769
770
0
    if (bottom_message && bottom_message[0]) {
771
0
      int bottom_message_length = strlen(bottom_message);
772
0
      int x = GetWidth() - 3 - (bottom_message_length + 2);
773
774
0
      if (x > 0) {
775
0
        MoveCursor(x, GetHeight() - 1);
776
0
        PutChar('[');
777
0
        PutCString(bottom_message);
778
0
        PutChar(']');
779
0
      } else {
780
0
        MoveCursor(1, GetHeight() - 1);
781
0
        PutChar('[');
782
0
        PutCStringTruncated(1, bottom_message);
783
0
      }
784
0
    }
785
0
    if (attr)
786
0
      AttributeOff(attr);
787
0
  }
788
789
0
  virtual void Draw(bool force) {
790
0
    if (m_delegate_sp && m_delegate_sp->WindowDelegateDraw(*this, force))
791
0
      return;
792
793
0
    for (auto &subwindow_sp : m_subwindows)
794
0
      subwindow_sp->Draw(force);
795
0
  }
796
797
0
  bool CreateHelpSubwindow() {
798
0
    if (m_delegate_sp) {
799
0
      const char *text = m_delegate_sp->WindowDelegateGetHelpText();
800
0
      KeyHelp *key_help = m_delegate_sp->WindowDelegateGetKeyHelp();
801
0
      if ((text && text[0]) || key_help) {
802
0
        std::unique_ptr<HelpDialogDelegate> help_delegate_up(
803
0
            new HelpDialogDelegate(text, key_help));
804
0
        const size_t num_lines = help_delegate_up->GetNumLines();
805
0
        const size_t max_length = help_delegate_up->GetMaxLineLength();
806
0
        Rect bounds = GetBounds();
807
0
        bounds.Inset(1, 1);
808
0
        if (max_length + 4 < static_cast<size_t>(bounds.size.width)) {
809
0
          bounds.origin.x += (bounds.size.width - max_length + 4) / 2;
810
0
          bounds.size.width = max_length + 4;
811
0
        } else {
812
0
          if (bounds.size.width > 100) {
813
0
            const int inset_w = bounds.size.width / 4;
814
0
            bounds.origin.x += inset_w;
815
0
            bounds.size.width -= 2 * inset_w;
816
0
          }
817
0
        }
818
819
0
        if (num_lines + 2 < static_cast<size_t>(bounds.size.height)) {
820
0
          bounds.origin.y += (bounds.size.height - num_lines + 2) / 2;
821
0
          bounds.size.height = num_lines + 2;
822
0
        } else {
823
0
          if (bounds.size.height > 100) {
824
0
            const int inset_h = bounds.size.height / 4;
825
0
            bounds.origin.y += inset_h;
826
0
            bounds.size.height -= 2 * inset_h;
827
0
          }
828
0
        }
829
0
        WindowSP help_window_sp;
830
0
        Window *parent_window = GetParent();
831
0
        if (parent_window)
832
0
          help_window_sp = parent_window->CreateSubWindow("Help", bounds, true);
833
0
        else
834
0
          help_window_sp = CreateSubWindow("Help", bounds, true);
835
0
        help_window_sp->SetDelegate(
836
0
            WindowDelegateSP(help_delegate_up.release()));
837
0
        return true;
838
0
      }
839
0
    }
840
0
    return false;
841
0
  }
842
843
0
  virtual HandleCharResult HandleChar(int key) {
844
    // Always check the active window first
845
0
    HandleCharResult result = eKeyNotHandled;
846
0
    WindowSP active_window_sp = GetActiveWindow();
847
0
    if (active_window_sp) {
848
0
      result = active_window_sp->HandleChar(key);
849
0
      if (result != eKeyNotHandled)
850
0
        return result;
851
0
    }
852
853
0
    if (m_delegate_sp) {
854
0
      result = m_delegate_sp->WindowDelegateHandleChar(*this, key);
855
0
      if (result != eKeyNotHandled)
856
0
        return result;
857
0
    }
858
859
    // Then check for any windows that want any keys that weren't handled. This
860
    // is typically only for a menubar. Make a copy of the subwindows in case
861
    // any HandleChar() functions muck with the subwindows. If we don't do
862
    // this, we can crash when iterating over the subwindows.
863
0
    Windows subwindows(m_subwindows);
864
0
    for (auto subwindow_sp : subwindows) {
865
0
      if (!subwindow_sp->m_can_activate) {
866
0
        HandleCharResult result = subwindow_sp->HandleChar(key);
867
0
        if (result != eKeyNotHandled)
868
0
          return result;
869
0
      }
870
0
    }
871
872
0
    return eKeyNotHandled;
873
0
  }
874
875
0
  WindowSP GetActiveWindow() {
876
0
    if (!m_subwindows.empty()) {
877
0
      if (m_curr_active_window_idx >= m_subwindows.size()) {
878
0
        if (m_prev_active_window_idx < m_subwindows.size()) {
879
0
          m_curr_active_window_idx = m_prev_active_window_idx;
880
0
          m_prev_active_window_idx = UINT32_MAX;
881
0
        } else if (IsActive()) {
882
0
          m_prev_active_window_idx = UINT32_MAX;
883
0
          m_curr_active_window_idx = UINT32_MAX;
884
885
          // Find first window that wants to be active if this window is active
886
0
          const size_t num_subwindows = m_subwindows.size();
887
0
          for (size_t i = 0; i < num_subwindows; ++i) {
888
0
            if (m_subwindows[i]->GetCanBeActive()) {
889
0
              m_curr_active_window_idx = i;
890
0
              break;
891
0
            }
892
0
          }
893
0
        }
894
0
      }
895
896
0
      if (m_curr_active_window_idx < m_subwindows.size())
897
0
        return m_subwindows[m_curr_active_window_idx];
898
0
    }
899
0
    return WindowSP();
900
0
  }
901
902
0
  bool GetCanBeActive() const { return m_can_activate; }
903
904
0
  void SetCanBeActive(bool b) { m_can_activate = b; }
905
906
0
  void SetDelegate(const WindowDelegateSP &delegate_sp) {
907
0
    m_delegate_sp = delegate_sp;
908
0
  }
909
910
0
  Window *GetParent() const { return m_parent; }
911
912
0
  bool IsActive() const {
913
0
    if (m_parent)
914
0
      return m_parent->GetActiveWindow().get() == this;
915
0
    else
916
0
      return true; // Top level window is always active
917
0
  }
918
919
0
  void SelectNextWindowAsActive() {
920
    // Move active focus to next window
921
0
    const int num_subwindows = m_subwindows.size();
922
0
    int start_idx = 0;
923
0
    if (m_curr_active_window_idx != UINT32_MAX) {
924
0
      m_prev_active_window_idx = m_curr_active_window_idx;
925
0
      start_idx = m_curr_active_window_idx + 1;
926
0
    }
927
0
    for (int idx = start_idx; idx < num_subwindows; ++idx) {
928
0
      if (m_subwindows[idx]->GetCanBeActive()) {
929
0
        m_curr_active_window_idx = idx;
930
0
        return;
931
0
      }
932
0
    }
933
0
    for (int idx = 0; idx < start_idx; ++idx) {
934
0
      if (m_subwindows[idx]->GetCanBeActive()) {
935
0
        m_curr_active_window_idx = idx;
936
0
        break;
937
0
      }
938
0
    }
939
0
  }
940
941
0
  void SelectPreviousWindowAsActive() {
942
    // Move active focus to previous window
943
0
    const int num_subwindows = m_subwindows.size();
944
0
    int start_idx = num_subwindows - 1;
945
0
    if (m_curr_active_window_idx != UINT32_MAX) {
946
0
      m_prev_active_window_idx = m_curr_active_window_idx;
947
0
      start_idx = m_curr_active_window_idx - 1;
948
0
    }
949
0
    for (int idx = start_idx; idx >= 0; --idx) {
950
0
      if (m_subwindows[idx]->GetCanBeActive()) {
951
0
        m_curr_active_window_idx = idx;
952
0
        return;
953
0
      }
954
0
    }
955
0
    for (int idx = num_subwindows - 1; idx > start_idx; --idx) {
956
0
      if (m_subwindows[idx]->GetCanBeActive()) {
957
0
        m_curr_active_window_idx = idx;
958
0
        break;
959
0
      }
960
0
    }
961
0
  }
962
963
0
  const char *GetName() const { return m_name.c_str(); }
964
965
protected:
966
  std::string m_name;
967
  PANEL *m_panel;
968
  Window *m_parent;
969
  Windows m_subwindows;
970
  WindowDelegateSP m_delegate_sp;
971
  uint32_t m_curr_active_window_idx;
972
  uint32_t m_prev_active_window_idx;
973
  bool m_delete;
974
  bool m_needs_update;
975
  bool m_can_activate;
976
  bool m_is_subwin;
977
978
private:
979
  Window(const Window &) = delete;
980
  const Window &operator=(const Window &) = delete;
981
};
982
983
/////////
984
// Forms
985
/////////
986
987
// A scroll context defines a vertical region that needs to be visible in a
988
// scrolling area. The region is defined by the index of the start and end lines
989
// of the region. The start and end lines may be equal, in which case, the
990
// region is a single line.
991
struct ScrollContext {
992
  int start;
993
  int end;
994
995
0
  ScrollContext(int line) : start(line), end(line) {}
996
0
  ScrollContext(int _start, int _end) : start(_start), end(_end) {}
997
998
0
  void Offset(int offset) {
999
0
    start += offset;
1000
0
    end += offset;
1001
0
  }
1002
};
1003
1004
class FieldDelegate {
1005
public:
1006
0
  virtual ~FieldDelegate() = default;
1007
1008
  // Returns the number of lines needed to draw the field. The draw method will
1009
  // be given a surface that have exactly this number of lines.
1010
  virtual int FieldDelegateGetHeight() = 0;
1011
1012
  // Returns the scroll context in the local coordinates of the field. By
1013
  // default, the scroll context spans the whole field. Bigger fields with
1014
  // internal navigation should override this method to provide a finer context.
1015
  // Typical override methods would first get the scroll context of the internal
1016
  // element then add the offset of the element in the field.
1017
0
  virtual ScrollContext FieldDelegateGetScrollContext() {
1018
0
    return ScrollContext(0, FieldDelegateGetHeight() - 1);
1019
0
  }
1020
1021
  // Draw the field in the given subpad surface. The surface have a height that
1022
  // is equal to the height returned by FieldDelegateGetHeight(). If the field
1023
  // is selected in the form window, then is_selected will be true.
1024
  virtual void FieldDelegateDraw(Surface &surface, bool is_selected) = 0;
1025
1026
  // Handle the key that wasn't handled by the form window or a container field.
1027
0
  virtual HandleCharResult FieldDelegateHandleChar(int key) {
1028
0
    return eKeyNotHandled;
1029
0
  }
1030
1031
  // This is executed once the user exists the field, that is, once the user
1032
  // navigates to the next or the previous field. This is particularly useful to
1033
  // do in-field validation and error setting. Fields with internal navigation
1034
  // should call this method on their fields.
1035
0
  virtual void FieldDelegateExitCallback() {}
1036
1037
  // Fields may have internal navigation, for instance, a List Field have
1038
  // multiple internal elements, which needs to be navigated. To allow for this
1039
  // mechanism, the window shouldn't handle the navigation keys all the time,
1040
  // and instead call the key handing method of the selected field. It should
1041
  // only handle the navigation keys when the field contains a single element or
1042
  // have the last or first element selected depending on if the user is
1043
  // navigating forward or backward. Additionally, once a field is selected in
1044
  // the forward or backward direction, its first or last internal element
1045
  // should be selected. The following methods implements those mechanisms.
1046
1047
  // Returns true if the first element in the field is selected or if the field
1048
  // contains a single element.
1049
0
  virtual bool FieldDelegateOnFirstOrOnlyElement() { return true; }
1050
1051
  // Returns true if the last element in the field is selected or if the field
1052
  // contains a single element.
1053
0
  virtual bool FieldDelegateOnLastOrOnlyElement() { return true; }
1054
1055
  // Select the first element in the field if multiple elements exists.
1056
0
  virtual void FieldDelegateSelectFirstElement() {}
1057
1058
  // Select the last element in the field if multiple elements exists.
1059
0
  virtual void FieldDelegateSelectLastElement() {}
1060
1061
  // Returns true if the field has an error, false otherwise.
1062
0
  virtual bool FieldDelegateHasError() { return false; }
1063
1064
0
  bool FieldDelegateIsVisible() { return m_is_visible; }
1065
1066
0
  void FieldDelegateHide() { m_is_visible = false; }
1067
1068
0
  void FieldDelegateShow() { m_is_visible = true; }
1069
1070
protected:
1071
  bool m_is_visible = true;
1072
};
1073
1074
typedef std::unique_ptr<FieldDelegate> FieldDelegateUP;
1075
1076
class TextFieldDelegate : public FieldDelegate {
1077
public:
1078
  TextFieldDelegate(const char *label, const char *content, bool required)
1079
      : m_label(label), m_required(required), m_cursor_position(0),
1080
0
        m_first_visibile_char(0) {
1081
0
    if (content)
1082
0
      m_content = content;
1083
0
  }
1084
1085
  // Text fields are drawn as titled boxes of a single line, with a possible
1086
  // error messages at the end.
1087
  //
1088
  // __[Label]___________
1089
  // |                  |
1090
  // |__________________|
1091
  // - Error message if it exists.
1092
1093
  // The text field has a height of 3 lines. 2 lines for borders and 1 line for
1094
  // the content.
1095
0
  int GetFieldHeight() { return 3; }
1096
1097
  // The text field has a full height of 3 or 4 lines. 3 lines for the actual
1098
  // field and an optional line for an error if it exists.
1099
0
  int FieldDelegateGetHeight() override {
1100
0
    int height = GetFieldHeight();
1101
0
    if (FieldDelegateHasError())
1102
0
      height++;
1103
0
    return height;
1104
0
  }
1105
1106
  // Get the cursor X position in the surface coordinate.
1107
0
  int GetCursorXPosition() { return m_cursor_position - m_first_visibile_char; }
1108
1109
0
  int GetContentLength() { return m_content.length(); }
1110
1111
0
  void DrawContent(Surface &surface, bool is_selected) {
1112
0
    UpdateScrolling(surface.GetWidth());
1113
1114
0
    surface.MoveCursor(0, 0);
1115
0
    const char *text = m_content.c_str() + m_first_visibile_char;
1116
0
    surface.PutCString(text, surface.GetWidth());
1117
1118
    // Highlight the cursor.
1119
0
    surface.MoveCursor(GetCursorXPosition(), 0);
1120
0
    if (is_selected)
1121
0
      surface.AttributeOn(A_REVERSE);
1122
0
    if (m_cursor_position == GetContentLength())
1123
      // Cursor is past the last character. Highlight an empty space.
1124
0
      surface.PutChar(' ');
1125
0
    else
1126
0
      surface.PutChar(m_content[m_cursor_position]);
1127
0
    if (is_selected)
1128
0
      surface.AttributeOff(A_REVERSE);
1129
0
  }
1130
1131
0
  void DrawField(Surface &surface, bool is_selected) {
1132
0
    surface.TitledBox(m_label.c_str());
1133
1134
0
    Rect content_bounds = surface.GetFrame();
1135
0
    content_bounds.Inset(1, 1);
1136
0
    Surface content_surface = surface.SubSurface(content_bounds);
1137
1138
0
    DrawContent(content_surface, is_selected);
1139
0
  }
1140
1141
0
  void DrawError(Surface &surface) {
1142
0
    if (!FieldDelegateHasError())
1143
0
      return;
1144
0
    surface.MoveCursor(0, 0);
1145
0
    surface.AttributeOn(COLOR_PAIR(RedOnBlack));
1146
0
    surface.PutChar(ACS_DIAMOND);
1147
0
    surface.PutChar(' ');
1148
0
    surface.PutCStringTruncated(1, GetError().c_str());
1149
0
    surface.AttributeOff(COLOR_PAIR(RedOnBlack));
1150
0
  }
1151
1152
0
  void FieldDelegateDraw(Surface &surface, bool is_selected) override {
1153
0
    Rect frame = surface.GetFrame();
1154
0
    Rect field_bounds, error_bounds;
1155
0
    frame.HorizontalSplit(GetFieldHeight(), field_bounds, error_bounds);
1156
0
    Surface field_surface = surface.SubSurface(field_bounds);
1157
0
    Surface error_surface = surface.SubSurface(error_bounds);
1158
1159
0
    DrawField(field_surface, is_selected);
1160
0
    DrawError(error_surface);
1161
0
  }
1162
1163
  // Get the position of the last visible character.
1164
0
  int GetLastVisibleCharPosition(int width) {
1165
0
    int position = m_first_visibile_char + width - 1;
1166
0
    return std::min(position, GetContentLength());
1167
0
  }
1168
1169
0
  void UpdateScrolling(int width) {
1170
0
    if (m_cursor_position < m_first_visibile_char) {
1171
0
      m_first_visibile_char = m_cursor_position;
1172
0
      return;
1173
0
    }
1174
1175
0
    if (m_cursor_position > GetLastVisibleCharPosition(width))
1176
0
      m_first_visibile_char = m_cursor_position - (width - 1);
1177
0
  }
1178
1179
  // The cursor is allowed to move one character past the string.
1180
  // m_cursor_position is in range [0, GetContentLength()].
1181
0
  void MoveCursorRight() {
1182
0
    if (m_cursor_position < GetContentLength())
1183
0
      m_cursor_position++;
1184
0
  }
1185
1186
0
  void MoveCursorLeft() {
1187
0
    if (m_cursor_position > 0)
1188
0
      m_cursor_position--;
1189
0
  }
1190
1191
0
  void MoveCursorToStart() { m_cursor_position = 0; }
1192
1193
0
  void MoveCursorToEnd() { m_cursor_position = GetContentLength(); }
1194
1195
0
  void ScrollLeft() {
1196
0
    if (m_first_visibile_char > 0)
1197
0
      m_first_visibile_char--;
1198
0
  }
1199
1200
  // Insert a character at the current cursor position and advance the cursor
1201
  // position.
1202
0
  void InsertChar(char character) {
1203
0
    m_content.insert(m_cursor_position, 1, character);
1204
0
    m_cursor_position++;
1205
0
    ClearError();
1206
0
  }
1207
1208
  // Remove the character before the cursor position, retreat the cursor
1209
  // position, and scroll left.
1210
0
  void RemovePreviousChar() {
1211
0
    if (m_cursor_position == 0)
1212
0
      return;
1213
1214
0
    m_content.erase(m_cursor_position - 1, 1);
1215
0
    m_cursor_position--;
1216
0
    ScrollLeft();
1217
0
    ClearError();
1218
0
  }
1219
1220
  // Remove the character after the cursor position.
1221
0
  void RemoveNextChar() {
1222
0
    if (m_cursor_position == GetContentLength())
1223
0
      return;
1224
1225
0
    m_content.erase(m_cursor_position, 1);
1226
0
    ClearError();
1227
0
  }
1228
1229
  // Clear characters from the current cursor position to the end.
1230
0
  void ClearToEnd() {
1231
0
    m_content.erase(m_cursor_position);
1232
0
    ClearError();
1233
0
  }
1234
1235
0
  void Clear() {
1236
0
    m_content.clear();
1237
0
    m_cursor_position = 0;
1238
0
    ClearError();
1239
0
  }
1240
1241
  // True if the key represents a char that can be inserted in the field
1242
  // content, false otherwise.
1243
0
  virtual bool IsAcceptableChar(int key) {
1244
    // The behavior of isprint is undefined when the value is not representable
1245
    // as an unsigned char. So explicitly check for non-ascii key codes.
1246
0
    if (key > 127)
1247
0
      return false;
1248
0
    return isprint(key);
1249
0
  }
1250
1251
0
  HandleCharResult FieldDelegateHandleChar(int key) override {
1252
0
    if (IsAcceptableChar(key)) {
1253
0
      ClearError();
1254
0
      InsertChar((char)key);
1255
0
      return eKeyHandled;
1256
0
    }
1257
1258
0
    switch (key) {
1259
0
    case KEY_HOME:
1260
0
    case KEY_CTRL_A:
1261
0
      MoveCursorToStart();
1262
0
      return eKeyHandled;
1263
0
    case KEY_END:
1264
0
    case KEY_CTRL_E:
1265
0
      MoveCursorToEnd();
1266
0
      return eKeyHandled;
1267
0
    case KEY_RIGHT:
1268
0
    case KEY_SF:
1269
0
      MoveCursorRight();
1270
0
      return eKeyHandled;
1271
0
    case KEY_LEFT:
1272
0
    case KEY_SR:
1273
0
      MoveCursorLeft();
1274
0
      return eKeyHandled;
1275
0
    case KEY_BACKSPACE:
1276
0
    case KEY_DELETE:
1277
0
      RemovePreviousChar();
1278
0
      return eKeyHandled;
1279
0
    case KEY_DC:
1280
0
      RemoveNextChar();
1281
0
      return eKeyHandled;
1282
0
    case KEY_EOL:
1283
0
    case KEY_CTRL_K:
1284
0
      ClearToEnd();
1285
0
      return eKeyHandled;
1286
0
    case KEY_DL:
1287
0
    case KEY_CLEAR:
1288
0
      Clear();
1289
0
      return eKeyHandled;
1290
0
    default:
1291
0
      break;
1292
0
    }
1293
0
    return eKeyNotHandled;
1294
0
  }
1295
1296
0
  bool FieldDelegateHasError() override { return !m_error.empty(); }
1297
1298
0
  void FieldDelegateExitCallback() override {
1299
0
    if (!IsSpecified() && m_required)
1300
0
      SetError("This field is required!");
1301
0
  }
1302
1303
0
  bool IsSpecified() { return !m_content.empty(); }
1304
1305
0
  void ClearError() { m_error.clear(); }
1306
1307
0
  const std::string &GetError() { return m_error; }
1308
1309
0
  void SetError(const char *error) { m_error = error; }
1310
1311
0
  const std::string &GetText() { return m_content; }
1312
1313
0
  void SetText(const char *text) {
1314
0
    if (text == nullptr) {
1315
0
      m_content.clear();
1316
0
      return;
1317
0
    }
1318
0
    m_content = text;
1319
0
  }
1320
1321
protected:
1322
  std::string m_label;
1323
  bool m_required;
1324
  // The position of the top left corner character of the border.
1325
  std::string m_content;
1326
  // The cursor position in the content string itself. Can be in the range
1327
  // [0, GetContentLength()].
1328
  int m_cursor_position;
1329
  // The index of the first visible character in the content.
1330
  int m_first_visibile_char;
1331
  // Optional error message. If empty, field is considered to have no error.
1332
  std::string m_error;
1333
};
1334
1335
class IntegerFieldDelegate : public TextFieldDelegate {
1336
public:
1337
  IntegerFieldDelegate(const char *label, int content, bool required)
1338
0
      : TextFieldDelegate(label, std::to_string(content).c_str(), required) {}
1339
1340
  // Only accept digits.
1341
0
  bool IsAcceptableChar(int key) override { return isdigit(key); }
1342
1343
  // Returns the integer content of the field.
1344
0
  int GetInteger() { return std::stoi(m_content); }
1345
};
1346
1347
class FileFieldDelegate : public TextFieldDelegate {
1348
public:
1349
  FileFieldDelegate(const char *label, const char *content, bool need_to_exist,
1350
                    bool required)
1351
      : TextFieldDelegate(label, content, required),
1352
0
        m_need_to_exist(need_to_exist) {}
1353
1354
0
  void FieldDelegateExitCallback() override {
1355
0
    TextFieldDelegate::FieldDelegateExitCallback();
1356
0
    if (!IsSpecified())
1357
0
      return;
1358
1359
0
    if (!m_need_to_exist)
1360
0
      return;
1361
1362
0
    FileSpec file = GetResolvedFileSpec();
1363
0
    if (!FileSystem::Instance().Exists(file)) {
1364
0
      SetError("File doesn't exist!");
1365
0
      return;
1366
0
    }
1367
0
    if (FileSystem::Instance().IsDirectory(file)) {
1368
0
      SetError("Not a file!");
1369
0
      return;
1370
0
    }
1371
0
  }
1372
1373
0
  FileSpec GetFileSpec() {
1374
0
    FileSpec file_spec(GetPath());
1375
0
    return file_spec;
1376
0
  }
1377
1378
0
  FileSpec GetResolvedFileSpec() {
1379
0
    FileSpec file_spec(GetPath());
1380
0
    FileSystem::Instance().Resolve(file_spec);
1381
0
    return file_spec;
1382
0
  }
1383
1384
0
  const std::string &GetPath() { return m_content; }
1385
1386
protected:
1387
  bool m_need_to_exist;
1388
};
1389
1390
class DirectoryFieldDelegate : public TextFieldDelegate {
1391
public:
1392
  DirectoryFieldDelegate(const char *label, const char *content,
1393
                         bool need_to_exist, bool required)
1394
      : TextFieldDelegate(label, content, required),
1395
0
        m_need_to_exist(need_to_exist) {}
1396
1397
0
  void FieldDelegateExitCallback() override {
1398
0
    TextFieldDelegate::FieldDelegateExitCallback();
1399
0
    if (!IsSpecified())
1400
0
      return;
1401
1402
0
    if (!m_need_to_exist)
1403
0
      return;
1404
1405
0
    FileSpec file = GetResolvedFileSpec();
1406
0
    if (!FileSystem::Instance().Exists(file)) {
1407
0
      SetError("Directory doesn't exist!");
1408
0
      return;
1409
0
    }
1410
0
    if (!FileSystem::Instance().IsDirectory(file)) {
1411
0
      SetError("Not a directory!");
1412
0
      return;
1413
0
    }
1414
0
  }
1415
1416
0
  FileSpec GetFileSpec() {
1417
0
    FileSpec file_spec(GetPath());
1418
0
    return file_spec;
1419
0
  }
1420
1421
0
  FileSpec GetResolvedFileSpec() {
1422
0
    FileSpec file_spec(GetPath());
1423
0
    FileSystem::Instance().Resolve(file_spec);
1424
0
    return file_spec;
1425
0
  }
1426
1427
0
  const std::string &GetPath() { return m_content; }
1428
1429
protected:
1430
  bool m_need_to_exist;
1431
};
1432
1433
class ArchFieldDelegate : public TextFieldDelegate {
1434
public:
1435
  ArchFieldDelegate(const char *label, const char *content, bool required)
1436
0
      : TextFieldDelegate(label, content, required) {}
1437
1438
0
  void FieldDelegateExitCallback() override {
1439
0
    TextFieldDelegate::FieldDelegateExitCallback();
1440
0
    if (!IsSpecified())
1441
0
      return;
1442
1443
0
    if (!GetArchSpec().IsValid())
1444
0
      SetError("Not a valid arch!");
1445
0
  }
1446
1447
0
  const std::string &GetArchString() { return m_content; }
1448
1449
0
  ArchSpec GetArchSpec() { return ArchSpec(GetArchString()); }
1450
};
1451
1452
class BooleanFieldDelegate : public FieldDelegate {
1453
public:
1454
  BooleanFieldDelegate(const char *label, bool content)
1455
0
      : m_label(label), m_content(content) {}
1456
1457
  // Boolean fields are drawn as checkboxes.
1458
  //
1459
  // [X] Label  or [ ] Label
1460
1461
  // Boolean fields are have a single line.
1462
0
  int FieldDelegateGetHeight() override { return 1; }
1463
1464
0
  void FieldDelegateDraw(Surface &surface, bool is_selected) override {
1465
0
    surface.MoveCursor(0, 0);
1466
0
    surface.PutChar('[');
1467
0
    if (is_selected)
1468
0
      surface.AttributeOn(A_REVERSE);
1469
0
    surface.PutChar(m_content ? ACS_DIAMOND : ' ');
1470
0
    if (is_selected)
1471
0
      surface.AttributeOff(A_REVERSE);
1472
0
    surface.PutChar(']');
1473
0
    surface.PutChar(' ');
1474
0
    surface.PutCString(m_label.c_str());
1475
0
  }
1476
1477
0
  void ToggleContent() { m_content = !m_content; }
1478
1479
0
  void SetContentToTrue() { m_content = true; }
1480
1481
0
  void SetContentToFalse() { m_content = false; }
1482
1483
0
  HandleCharResult FieldDelegateHandleChar(int key) override {
1484
0
    switch (key) {
1485
0
    case 't':
1486
0
    case '1':
1487
0
      SetContentToTrue();
1488
0
      return eKeyHandled;
1489
0
    case 'f':
1490
0
    case '0':
1491
0
      SetContentToFalse();
1492
0
      return eKeyHandled;
1493
0
    case ' ':
1494
0
    case '\r':
1495
0
    case '\n':
1496
0
    case KEY_ENTER:
1497
0
      ToggleContent();
1498
0
      return eKeyHandled;
1499
0
    default:
1500
0
      break;
1501
0
    }
1502
0
    return eKeyNotHandled;
1503
0
  }
1504
1505
  // Returns the boolean content of the field.
1506
0
  bool GetBoolean() { return m_content; }
1507
1508
protected:
1509
  std::string m_label;
1510
  bool m_content;
1511
};
1512
1513
class ChoicesFieldDelegate : public FieldDelegate {
1514
public:
1515
  ChoicesFieldDelegate(const char *label, int number_of_visible_choices,
1516
                       std::vector<std::string> choices)
1517
      : m_label(label), m_number_of_visible_choices(number_of_visible_choices),
1518
0
        m_choices(choices), m_choice(0), m_first_visibile_choice(0) {}
1519
1520
  // Choices fields are drawn as titles boxses of a number of visible choices.
1521
  // The rest of the choices become visible as the user scroll. The selected
1522
  // choice is denoted by a diamond as the first character.
1523
  //
1524
  // __[Label]___________
1525
  // |-Choice 1         |
1526
  // | Choice 2         |
1527
  // | Choice 3         |
1528
  // |__________________|
1529
1530
  // Choices field have two border characters plus the number of visible
1531
  // choices.
1532
0
  int FieldDelegateGetHeight() override {
1533
0
    return m_number_of_visible_choices + 2;
1534
0
  }
1535
1536
0
  int GetNumberOfChoices() { return m_choices.size(); }
1537
1538
  // Get the index of the last visible choice.
1539
0
  int GetLastVisibleChoice() {
1540
0
    int index = m_first_visibile_choice + m_number_of_visible_choices;
1541
0
    return std::min(index, GetNumberOfChoices()) - 1;
1542
0
  }
1543
1544
0
  void DrawContent(Surface &surface, bool is_selected) {
1545
0
    int choices_to_draw = GetLastVisibleChoice() - m_first_visibile_choice + 1;
1546
0
    for (int i = 0; i < choices_to_draw; i++) {
1547
0
      surface.MoveCursor(0, i);
1548
0
      int current_choice = m_first_visibile_choice + i;
1549
0
      const char *text = m_choices[current_choice].c_str();
1550
0
      bool highlight = is_selected && current_choice == m_choice;
1551
0
      if (highlight)
1552
0
        surface.AttributeOn(A_REVERSE);
1553
0
      surface.PutChar(current_choice == m_choice ? ACS_DIAMOND : ' ');
1554
0
      surface.PutCString(text);
1555
0
      if (highlight)
1556
0
        surface.AttributeOff(A_REVERSE);
1557
0
    }
1558
0
  }
1559
1560
0
  void FieldDelegateDraw(Surface &surface, bool is_selected) override {
1561
0
    UpdateScrolling();
1562
1563
0
    surface.TitledBox(m_label.c_str());
1564
1565
0
    Rect content_bounds = surface.GetFrame();
1566
0
    content_bounds.Inset(1, 1);
1567
0
    Surface content_surface = surface.SubSurface(content_bounds);
1568
1569
0
    DrawContent(content_surface, is_selected);
1570
0
  }
1571
1572
0
  void SelectPrevious() {
1573
0
    if (m_choice > 0)
1574
0
      m_choice--;
1575
0
  }
1576
1577
0
  void SelectNext() {
1578
0
    if (m_choice < GetNumberOfChoices() - 1)
1579
0
      m_choice++;
1580
0
  }
1581
1582
0
  void UpdateScrolling() {
1583
0
    if (m_choice > GetLastVisibleChoice()) {
1584
0
      m_first_visibile_choice = m_choice - (m_number_of_visible_choices - 1);
1585
0
      return;
1586
0
    }
1587
1588
0
    if (m_choice < m_first_visibile_choice)
1589
0
      m_first_visibile_choice = m_choice;
1590
0
  }
1591
1592
0
  HandleCharResult FieldDelegateHandleChar(int key) override {
1593
0
    switch (key) {
1594
0
    case KEY_UP:
1595
0
      SelectPrevious();
1596
0
      return eKeyHandled;
1597
0
    case KEY_DOWN:
1598
0
      SelectNext();
1599
0
      return eKeyHandled;
1600
0
    default:
1601
0
      break;
1602
0
    }
1603
0
    return eKeyNotHandled;
1604
0
  }
1605
1606
  // Returns the content of the choice as a string.
1607
0
  std::string GetChoiceContent() { return m_choices[m_choice]; }
1608
1609
  // Returns the index of the choice.
1610
0
  int GetChoice() { return m_choice; }
1611
1612
0
  void SetChoice(const std::string &choice) {
1613
0
    for (int i = 0; i < GetNumberOfChoices(); i++) {
1614
0
      if (choice == m_choices[i]) {
1615
0
        m_choice = i;
1616
0
        return;
1617
0
      }
1618
0
    }
1619
0
  }
1620
1621
protected:
1622
  std::string m_label;
1623
  int m_number_of_visible_choices;
1624
  std::vector<std::string> m_choices;
1625
  // The index of the selected choice.
1626
  int m_choice;
1627
  // The index of the first visible choice in the field.
1628
  int m_first_visibile_choice;
1629
};
1630
1631
class PlatformPluginFieldDelegate : public ChoicesFieldDelegate {
1632
public:
1633
  PlatformPluginFieldDelegate(Debugger &debugger)
1634
0
      : ChoicesFieldDelegate("Platform Plugin", 3, GetPossiblePluginNames()) {
1635
0
    PlatformSP platform_sp = debugger.GetPlatformList().GetSelectedPlatform();
1636
0
    if (platform_sp)
1637
0
      SetChoice(platform_sp->GetName().AsCString());
1638
0
  }
1639
1640
0
  std::vector<std::string> GetPossiblePluginNames() {
1641
0
    std::vector<std::string> names;
1642
0
    size_t i = 0;
1643
0
    for (llvm::StringRef name =
1644
0
             PluginManager::GetPlatformPluginNameAtIndex(i++);
1645
0
         !name.empty(); name = PluginManager::GetProcessPluginNameAtIndex(i++))
1646
0
      names.push_back(name.str());
1647
0
    return names;
1648
0
  }
1649
1650
0
  std::string GetPluginName() {
1651
0
    std::string plugin_name = GetChoiceContent();
1652
0
    return plugin_name;
1653
0
  }
1654
};
1655
1656
class ProcessPluginFieldDelegate : public ChoicesFieldDelegate {
1657
public:
1658
  ProcessPluginFieldDelegate()
1659
0
      : ChoicesFieldDelegate("Process Plugin", 3, GetPossiblePluginNames()) {}
1660
1661
0
  std::vector<std::string> GetPossiblePluginNames() {
1662
0
    std::vector<std::string> names;
1663
0
    names.push_back("<default>");
1664
1665
0
    size_t i = 0;
1666
0
    for (llvm::StringRef name = PluginManager::GetProcessPluginNameAtIndex(i++);
1667
0
         !name.empty(); name = PluginManager::GetProcessPluginNameAtIndex(i++))
1668
0
      names.push_back(name.str());
1669
0
    return names;
1670
0
  }
1671
1672
0
  std::string GetPluginName() {
1673
0
    std::string plugin_name = GetChoiceContent();
1674
0
    if (plugin_name == "<default>")
1675
0
      return "";
1676
0
    return plugin_name;
1677
0
  }
1678
};
1679
1680
class LazyBooleanFieldDelegate : public ChoicesFieldDelegate {
1681
public:
1682
  LazyBooleanFieldDelegate(const char *label, const char *calculate_label)
1683
0
      : ChoicesFieldDelegate(label, 3, GetPossibleOptions(calculate_label)) {}
1684
1685
  static constexpr const char *kNo = "No";
1686
  static constexpr const char *kYes = "Yes";
1687
1688
0
  std::vector<std::string> GetPossibleOptions(const char *calculate_label) {
1689
0
    std::vector<std::string> options;
1690
0
    options.push_back(calculate_label);
1691
0
    options.push_back(kYes);
1692
0
    options.push_back(kNo);
1693
0
    return options;
1694
0
  }
1695
1696
0
  LazyBool GetLazyBoolean() {
1697
0
    std::string choice = GetChoiceContent();
1698
0
    if (choice == kNo)
1699
0
      return eLazyBoolNo;
1700
0
    else if (choice == kYes)
1701
0
      return eLazyBoolYes;
1702
0
    else
1703
0
      return eLazyBoolCalculate;
1704
0
  }
1705
};
1706
1707
template <class T> class ListFieldDelegate : public FieldDelegate {
1708
public:
1709
  ListFieldDelegate(const char *label, T default_field)
1710
      : m_label(label), m_default_field(default_field), m_selection_index(0),
1711
0
        m_selection_type(SelectionType::NewButton) {}
Unexecuted instantiation: curses::ListFieldDelegate<curses::TextFieldDelegate>::ListFieldDelegate(char const*, curses::TextFieldDelegate)
Unexecuted instantiation: curses::ListFieldDelegate<curses::EnvironmentVariableFieldDelegate>::ListFieldDelegate(char const*, curses::EnvironmentVariableFieldDelegate)
1712
1713
  // Signify which element is selected. If a field or a remove button is
1714
  // selected, then m_selection_index signifies the particular field that
1715
  // is selected or the field that the remove button belongs to.
1716
  enum class SelectionType { Field, RemoveButton, NewButton };
1717
1718
  // A List field is drawn as a titled box of a number of other fields of the
1719
  // same type. Each field has a Remove button next to it that removes the
1720
  // corresponding field. Finally, the last line contains a New button to add a
1721
  // new field.
1722
  //
1723
  // __[Label]___________
1724
  // | Field 0 [Remove] |
1725
  // | Field 1 [Remove] |
1726
  // | Field 2 [Remove] |
1727
  // |      [New]       |
1728
  // |__________________|
1729
1730
  // List fields have two lines for border characters, 1 line for the New
1731
  // button, and the total height of the available fields.
1732
0
  int FieldDelegateGetHeight() override {
1733
    // 2 border characters.
1734
0
    int height = 2;
1735
    // Total height of the fields.
1736
0
    for (int i = 0; i < GetNumberOfFields(); i++) {
1737
0
      height += m_fields[i].FieldDelegateGetHeight();
1738
0
    }
1739
    // A line for the New button.
1740
0
    height++;
1741
0
    return height;
1742
0
  }
Unexecuted instantiation: curses::ListFieldDelegate<curses::TextFieldDelegate>::FieldDelegateGetHeight()
Unexecuted instantiation: curses::ListFieldDelegate<curses::EnvironmentVariableFieldDelegate>::FieldDelegateGetHeight()
1743
1744
0
  ScrollContext FieldDelegateGetScrollContext() override {
1745
0
    int height = FieldDelegateGetHeight();
1746
0
    if (m_selection_type == SelectionType::NewButton)
1747
0
      return ScrollContext(height - 2, height - 1);
1748
1749
0
    FieldDelegate &field = m_fields[m_selection_index];
1750
0
    ScrollContext context = field.FieldDelegateGetScrollContext();
1751
1752
    // Start at 1 because of the top border.
1753
0
    int offset = 1;
1754
0
    for (int i = 0; i < m_selection_index; i++) {
1755
0
      offset += m_fields[i].FieldDelegateGetHeight();
1756
0
    }
1757
0
    context.Offset(offset);
1758
1759
    // If the scroll context is touching the top border, include it in the
1760
    // context to show the label.
1761
0
    if (context.start == 1)
1762
0
      context.start--;
1763
1764
    // If the scroll context is touching the new button, include it as well as
1765
    // the bottom border in the context.
1766
0
    if (context.end == height - 3)
1767
0
      context.end += 2;
1768
1769
0
    return context;
1770
0
  }
Unexecuted instantiation: curses::ListFieldDelegate<curses::TextFieldDelegate>::FieldDelegateGetScrollContext()
Unexecuted instantiation: curses::ListFieldDelegate<curses::EnvironmentVariableFieldDelegate>::FieldDelegateGetScrollContext()
1771
1772
0
  void DrawRemoveButton(Surface &surface, int highlight) {
1773
0
    surface.MoveCursor(1, surface.GetHeight() / 2);
1774
0
    if (highlight)
1775
0
      surface.AttributeOn(A_REVERSE);
1776
0
    surface.PutCString("[Remove]");
1777
0
    if (highlight)
1778
0
      surface.AttributeOff(A_REVERSE);
1779
0
  }
Unexecuted instantiation: curses::ListFieldDelegate<curses::TextFieldDelegate>::DrawRemoveButton(curses::Surface&, int)
Unexecuted instantiation: curses::ListFieldDelegate<curses::EnvironmentVariableFieldDelegate>::DrawRemoveButton(curses::Surface&, int)
1780
1781
0
  void DrawFields(Surface &surface, bool is_selected) {
1782
0
    int line = 0;
1783
0
    int width = surface.GetWidth();
1784
0
    for (int i = 0; i < GetNumberOfFields(); i++) {
1785
0
      int height = m_fields[i].FieldDelegateGetHeight();
1786
0
      Rect bounds = Rect(Point(0, line), Size(width, height));
1787
0
      Rect field_bounds, remove_button_bounds;
1788
0
      bounds.VerticalSplit(bounds.size.width - sizeof(" [Remove]"),
1789
0
                           field_bounds, remove_button_bounds);
1790
0
      Surface field_surface = surface.SubSurface(field_bounds);
1791
0
      Surface remove_button_surface = surface.SubSurface(remove_button_bounds);
1792
1793
0
      bool is_element_selected = m_selection_index == i && is_selected;
1794
0
      bool is_field_selected =
1795
0
          is_element_selected && m_selection_type == SelectionType::Field;
1796
0
      bool is_remove_button_selected =
1797
0
          is_element_selected &&
1798
0
          m_selection_type == SelectionType::RemoveButton;
1799
0
      m_fields[i].FieldDelegateDraw(field_surface, is_field_selected);
1800
0
      DrawRemoveButton(remove_button_surface, is_remove_button_selected);
1801
1802
0
      line += height;
1803
0
    }
1804
0
  }
Unexecuted instantiation: curses::ListFieldDelegate<curses::TextFieldDelegate>::DrawFields(curses::Surface&, bool)
Unexecuted instantiation: curses::ListFieldDelegate<curses::EnvironmentVariableFieldDelegate>::DrawFields(curses::Surface&, bool)
1805
1806
0
  void DrawNewButton(Surface &surface, bool is_selected) {
1807
0
    const char *button_text = "[New]";
1808
0
    int x = (surface.GetWidth() - sizeof(button_text) - 1) / 2;
1809
0
    surface.MoveCursor(x, 0);
1810
0
    bool highlight =
1811
0
        is_selected && m_selection_type == SelectionType::NewButton;
1812
0
    if (highlight)
1813
0
      surface.AttributeOn(A_REVERSE);
1814
0
    surface.PutCString(button_text);
1815
0
    if (highlight)
1816
0
      surface.AttributeOff(A_REVERSE);
1817
0
  }
Unexecuted instantiation: curses::ListFieldDelegate<curses::TextFieldDelegate>::DrawNewButton(curses::Surface&, bool)
Unexecuted instantiation: curses::ListFieldDelegate<curses::EnvironmentVariableFieldDelegate>::DrawNewButton(curses::Surface&, bool)
1818
1819
0
  void FieldDelegateDraw(Surface &surface, bool is_selected) override {
1820
0
    surface.TitledBox(m_label.c_str());
1821
1822
0
    Rect content_bounds = surface.GetFrame();
1823
0
    content_bounds.Inset(1, 1);
1824
0
    Rect fields_bounds, new_button_bounds;
1825
0
    content_bounds.HorizontalSplit(content_bounds.size.height - 1,
1826
0
                                   fields_bounds, new_button_bounds);
1827
0
    Surface fields_surface = surface.SubSurface(fields_bounds);
1828
0
    Surface new_button_surface = surface.SubSurface(new_button_bounds);
1829
1830
0
    DrawFields(fields_surface, is_selected);
1831
0
    DrawNewButton(new_button_surface, is_selected);
1832
0
  }
Unexecuted instantiation: curses::ListFieldDelegate<curses::TextFieldDelegate>::FieldDelegateDraw(curses::Surface&, bool)
Unexecuted instantiation: curses::ListFieldDelegate<curses::EnvironmentVariableFieldDelegate>::FieldDelegateDraw(curses::Surface&, bool)
1833
1834
0
  void AddNewField() {
1835
0
    m_fields.push_back(m_default_field);
1836
0
    m_selection_index = GetNumberOfFields() - 1;
1837
0
    m_selection_type = SelectionType::Field;
1838
0
    FieldDelegate &field = m_fields[m_selection_index];
1839
0
    field.FieldDelegateSelectFirstElement();
1840
0
  }
Unexecuted instantiation: curses::ListFieldDelegate<curses::TextFieldDelegate>::AddNewField()
Unexecuted instantiation: curses::ListFieldDelegate<curses::EnvironmentVariableFieldDelegate>::AddNewField()
1841
1842
0
  void RemoveField() {
1843
0
    m_fields.erase(m_fields.begin() + m_selection_index);
1844
0
    if (m_selection_index != 0)
1845
0
      m_selection_index--;
1846
1847
0
    if (GetNumberOfFields() > 0) {
1848
0
      m_selection_type = SelectionType::Field;
1849
0
      FieldDelegate &field = m_fields[m_selection_index];
1850
0
      field.FieldDelegateSelectFirstElement();
1851
0
    } else
1852
0
      m_selection_type = SelectionType::NewButton;
1853
0
  }
Unexecuted instantiation: curses::ListFieldDelegate<curses::TextFieldDelegate>::RemoveField()
Unexecuted instantiation: curses::ListFieldDelegate<curses::EnvironmentVariableFieldDelegate>::RemoveField()
1854
1855
0
  HandleCharResult SelectNext(int key) {
1856
0
    if (m_selection_type == SelectionType::NewButton)
1857
0
      return eKeyNotHandled;
1858
1859
0
    if (m_selection_type == SelectionType::RemoveButton) {
1860
0
      if (m_selection_index == GetNumberOfFields() - 1) {
1861
0
        m_selection_type = SelectionType::NewButton;
1862
0
        return eKeyHandled;
1863
0
      }
1864
0
      m_selection_index++;
1865
0
      m_selection_type = SelectionType::Field;
1866
0
      FieldDelegate &next_field = m_fields[m_selection_index];
1867
0
      next_field.FieldDelegateSelectFirstElement();
1868
0
      return eKeyHandled;
1869
0
    }
1870
1871
0
    FieldDelegate &field = m_fields[m_selection_index];
1872
0
    if (!field.FieldDelegateOnLastOrOnlyElement()) {
1873
0
      return field.FieldDelegateHandleChar(key);
1874
0
    }
1875
1876
0
    field.FieldDelegateExitCallback();
1877
1878
0
    m_selection_type = SelectionType::RemoveButton;
1879
0
    return eKeyHandled;
1880
0
  }
Unexecuted instantiation: curses::ListFieldDelegate<curses::TextFieldDelegate>::SelectNext(int)
Unexecuted instantiation: curses::ListFieldDelegate<curses::EnvironmentVariableFieldDelegate>::SelectNext(int)
1881
1882
0
  HandleCharResult SelectPrevious(int key) {
1883
0
    if (FieldDelegateOnFirstOrOnlyElement())
1884
0
      return eKeyNotHandled;
1885
1886
0
    if (m_selection_type == SelectionType::RemoveButton) {
1887
0
      m_selection_type = SelectionType::Field;
1888
0
      FieldDelegate &field = m_fields[m_selection_index];
1889
0
      field.FieldDelegateSelectLastElement();
1890
0
      return eKeyHandled;
1891
0
    }
1892
1893
0
    if (m_selection_type == SelectionType::NewButton) {
1894
0
      m_selection_type = SelectionType::RemoveButton;
1895
0
      m_selection_index = GetNumberOfFields() - 1;
1896
0
      return eKeyHandled;
1897
0
    }
1898
1899
0
    FieldDelegate &field = m_fields[m_selection_index];
1900
0
    if (!field.FieldDelegateOnFirstOrOnlyElement()) {
1901
0
      return field.FieldDelegateHandleChar(key);
1902
0
    }
1903
1904
0
    field.FieldDelegateExitCallback();
1905
1906
0
    m_selection_type = SelectionType::RemoveButton;
1907
0
    m_selection_index--;
1908
0
    return eKeyHandled;
1909
0
  }
Unexecuted instantiation: curses::ListFieldDelegate<curses::TextFieldDelegate>::SelectPrevious(int)
Unexecuted instantiation: curses::ListFieldDelegate<curses::EnvironmentVariableFieldDelegate>::SelectPrevious(int)
1910
1911
  // If the last element of the field is selected and it didn't handle the key.
1912
  // Select the next field or new button if the selected field is the last one.
1913
0
  HandleCharResult SelectNextInList(int key) {
1914
0
    assert(m_selection_type == SelectionType::Field);
1915
1916
0
    FieldDelegate &field = m_fields[m_selection_index];
1917
0
    if (field.FieldDelegateHandleChar(key) == eKeyHandled)
1918
0
      return eKeyHandled;
1919
1920
0
    if (!field.FieldDelegateOnLastOrOnlyElement())
1921
0
      return eKeyNotHandled;
1922
1923
0
    field.FieldDelegateExitCallback();
1924
1925
0
    if (m_selection_index == GetNumberOfFields() - 1) {
1926
0
      m_selection_type = SelectionType::NewButton;
1927
0
      return eKeyHandled;
1928
0
    }
1929
1930
0
    m_selection_index++;
1931
0
    FieldDelegate &next_field = m_fields[m_selection_index];
1932
0
    next_field.FieldDelegateSelectFirstElement();
1933
0
    return eKeyHandled;
1934
0
  }
Unexecuted instantiation: curses::ListFieldDelegate<curses::TextFieldDelegate>::SelectNextInList(int)
Unexecuted instantiation: curses::ListFieldDelegate<curses::EnvironmentVariableFieldDelegate>::SelectNextInList(int)
1935
1936
0
  HandleCharResult FieldDelegateHandleChar(int key) override {
1937
0
    switch (key) {
1938
0
    case '\r':
1939
0
    case '\n':
1940
0
    case KEY_ENTER:
1941
0
      switch (m_selection_type) {
1942
0
      case SelectionType::NewButton:
1943
0
        AddNewField();
1944
0
        return eKeyHandled;
1945
0
      case SelectionType::RemoveButton:
1946
0
        RemoveField();
1947
0
        return eKeyHandled;
1948
0
      case SelectionType::Field:
1949
0
        return SelectNextInList(key);
1950
0
      }
1951
0
      break;
1952
0
    case '\t':
1953
0
      return SelectNext(key);
1954
0
    case KEY_SHIFT_TAB:
1955
0
      return SelectPrevious(key);
1956
0
    default:
1957
0
      break;
1958
0
    }
1959
1960
    // If the key wasn't handled and one of the fields is selected, pass the key
1961
    // to that field.
1962
0
    if (m_selection_type == SelectionType::Field) {
1963
0
      return m_fields[m_selection_index].FieldDelegateHandleChar(key);
1964
0
    }
1965
1966
0
    return eKeyNotHandled;
1967
0
  }
Unexecuted instantiation: curses::ListFieldDelegate<curses::TextFieldDelegate>::FieldDelegateHandleChar(int)
Unexecuted instantiation: curses::ListFieldDelegate<curses::EnvironmentVariableFieldDelegate>::FieldDelegateHandleChar(int)
1968
1969
0
  bool FieldDelegateOnLastOrOnlyElement() override {
1970
0
    if (m_selection_type == SelectionType::NewButton) {
1971
0
      return true;
1972
0
    }
1973
0
    return false;
1974
0
  }
Unexecuted instantiation: curses::ListFieldDelegate<curses::TextFieldDelegate>::FieldDelegateOnLastOrOnlyElement()
Unexecuted instantiation: curses::ListFieldDelegate<curses::EnvironmentVariableFieldDelegate>::FieldDelegateOnLastOrOnlyElement()
1975
1976
0
  bool FieldDelegateOnFirstOrOnlyElement() override {
1977
0
    if (m_selection_type == SelectionType::NewButton &&
1978
0
        GetNumberOfFields() == 0)
1979
0
      return true;
1980
1981
0
    if (m_selection_type == SelectionType::Field && m_selection_index == 0) {
1982
0
      FieldDelegate &field = m_fields[m_selection_index];
1983
0
      return field.FieldDelegateOnFirstOrOnlyElement();
1984
0
    }
1985
1986
0
    return false;
1987
0
  }
Unexecuted instantiation: curses::ListFieldDelegate<curses::TextFieldDelegate>::FieldDelegateOnFirstOrOnlyElement()
Unexecuted instantiation: curses::ListFieldDelegate<curses::EnvironmentVariableFieldDelegate>::FieldDelegateOnFirstOrOnlyElement()
1988
1989
0
  void FieldDelegateSelectFirstElement() override {
1990
0
    if (GetNumberOfFields() == 0) {
1991
0
      m_selection_type = SelectionType::NewButton;
1992
0
      return;
1993
0
    }
1994
1995
0
    m_selection_type = SelectionType::Field;
1996
0
    m_selection_index = 0;
1997
0
  }
Unexecuted instantiation: curses::ListFieldDelegate<curses::TextFieldDelegate>::FieldDelegateSelectFirstElement()
Unexecuted instantiation: curses::ListFieldDelegate<curses::EnvironmentVariableFieldDelegate>::FieldDelegateSelectFirstElement()
1998
1999
0
  void FieldDelegateSelectLastElement() override {
2000
0
    m_selection_type = SelectionType::NewButton;
2001
0
  }
Unexecuted instantiation: curses::ListFieldDelegate<curses::TextFieldDelegate>::FieldDelegateSelectLastElement()
Unexecuted instantiation: curses::ListFieldDelegate<curses::EnvironmentVariableFieldDelegate>::FieldDelegateSelectLastElement()
2002
2003
0
  int GetNumberOfFields() { return m_fields.size(); }
Unexecuted instantiation: curses::ListFieldDelegate<curses::TextFieldDelegate>::GetNumberOfFields()
Unexecuted instantiation: curses::ListFieldDelegate<curses::EnvironmentVariableFieldDelegate>::GetNumberOfFields()
2004
2005
  // Returns the form delegate at the current index.
2006
0
  T &GetField(int index) { return m_fields[index]; }
Unexecuted instantiation: curses::ListFieldDelegate<curses::TextFieldDelegate>::GetField(int)
Unexecuted instantiation: curses::ListFieldDelegate<curses::EnvironmentVariableFieldDelegate>::GetField(int)
2007
2008
protected:
2009
  std::string m_label;
2010
  // The default field delegate instance from which new field delegates will be
2011
  // created though a copy.
2012
  T m_default_field;
2013
  std::vector<T> m_fields;
2014
  int m_selection_index;
2015
  // See SelectionType class enum.
2016
  SelectionType m_selection_type;
2017
};
2018
2019
class ArgumentsFieldDelegate : public ListFieldDelegate<TextFieldDelegate> {
2020
public:
2021
  ArgumentsFieldDelegate()
2022
      : ListFieldDelegate("Arguments",
2023
0
                          TextFieldDelegate("Argument", "", false)) {}
2024
2025
0
  Args GetArguments() {
2026
0
    Args arguments;
2027
0
    for (int i = 0; i < GetNumberOfFields(); i++) {
2028
0
      arguments.AppendArgument(GetField(i).GetText());
2029
0
    }
2030
0
    return arguments;
2031
0
  }
2032
2033
0
  void AddArguments(const Args &arguments) {
2034
0
    for (size_t i = 0; i < arguments.GetArgumentCount(); i++) {
2035
0
      AddNewField();
2036
0
      TextFieldDelegate &field = GetField(GetNumberOfFields() - 1);
2037
0
      field.SetText(arguments.GetArgumentAtIndex(i));
2038
0
    }
2039
0
  }
2040
};
2041
2042
template <class KeyFieldDelegateType, class ValueFieldDelegateType>
2043
class MappingFieldDelegate : public FieldDelegate {
2044
public:
2045
  MappingFieldDelegate(KeyFieldDelegateType key_field,
2046
                       ValueFieldDelegateType value_field)
2047
      : m_key_field(key_field), m_value_field(value_field),
2048
0
        m_selection_type(SelectionType::Key) {}
2049
2050
  // Signify which element is selected. The key field or its value field.
2051
  enum class SelectionType { Key, Value };
2052
2053
  // A mapping field is drawn as two text fields with a right arrow in between.
2054
  // The first field stores the key of the mapping and the second stores the
2055
  // value if the mapping.
2056
  //
2057
  // __[Key]_____________   __[Value]___________
2058
  // |                  | > |                  |
2059
  // |__________________|   |__________________|
2060
  // - Error message if it exists.
2061
2062
  // The mapping field has a height that is equal to the maximum height between
2063
  // the key and value fields.
2064
0
  int FieldDelegateGetHeight() override {
2065
0
    return std::max(m_key_field.FieldDelegateGetHeight(),
2066
0
                    m_value_field.FieldDelegateGetHeight());
2067
0
  }
2068
2069
0
  void DrawArrow(Surface &surface) {
2070
0
    surface.MoveCursor(0, 1);
2071
0
    surface.PutChar(ACS_RARROW);
2072
0
  }
2073
2074
0
  void FieldDelegateDraw(Surface &surface, bool is_selected) override {
2075
0
    Rect bounds = surface.GetFrame();
2076
0
    Rect key_field_bounds, arrow_and_value_field_bounds;
2077
0
    bounds.VerticalSplit(bounds.size.width / 2, key_field_bounds,
2078
0
                         arrow_and_value_field_bounds);
2079
0
    Rect arrow_bounds, value_field_bounds;
2080
0
    arrow_and_value_field_bounds.VerticalSplit(1, arrow_bounds,
2081
0
                                               value_field_bounds);
2082
2083
0
    Surface key_field_surface = surface.SubSurface(key_field_bounds);
2084
0
    Surface arrow_surface = surface.SubSurface(arrow_bounds);
2085
0
    Surface value_field_surface = surface.SubSurface(value_field_bounds);
2086
2087
0
    bool key_is_selected =
2088
0
        m_selection_type == SelectionType::Key && is_selected;
2089
0
    m_key_field.FieldDelegateDraw(key_field_surface, key_is_selected);
2090
0
    DrawArrow(arrow_surface);
2091
0
    bool value_is_selected =
2092
0
        m_selection_type == SelectionType::Value && is_selected;
2093
0
    m_value_field.FieldDelegateDraw(value_field_surface, value_is_selected);
2094
0
  }
2095
2096
0
  HandleCharResult SelectNext(int key) {
2097
0
    if (FieldDelegateOnLastOrOnlyElement())
2098
0
      return eKeyNotHandled;
2099
2100
0
    if (!m_key_field.FieldDelegateOnLastOrOnlyElement()) {
2101
0
      return m_key_field.FieldDelegateHandleChar(key);
2102
0
    }
2103
2104
0
    m_key_field.FieldDelegateExitCallback();
2105
0
    m_selection_type = SelectionType::Value;
2106
0
    m_value_field.FieldDelegateSelectFirstElement();
2107
0
    return eKeyHandled;
2108
0
  }
2109
2110
0
  HandleCharResult SelectPrevious(int key) {
2111
0
    if (FieldDelegateOnFirstOrOnlyElement())
2112
0
      return eKeyNotHandled;
2113
2114
0
    if (!m_value_field.FieldDelegateOnFirstOrOnlyElement()) {
2115
0
      return m_value_field.FieldDelegateHandleChar(key);
2116
0
    }
2117
2118
0
    m_value_field.FieldDelegateExitCallback();
2119
0
    m_selection_type = SelectionType::Key;
2120
0
    m_key_field.FieldDelegateSelectLastElement();
2121
0
    return eKeyHandled;
2122
0
  }
2123
2124
  // If the value field is selected, pass the key to it. If the key field is
2125
  // selected, its last element is selected, and it didn't handle the key, then
2126
  // select its corresponding value field.
2127
0
  HandleCharResult SelectNextField(int key) {
2128
0
    if (m_selection_type == SelectionType::Value) {
2129
0
      return m_value_field.FieldDelegateHandleChar(key);
2130
0
    }
2131
2132
0
    if (m_key_field.FieldDelegateHandleChar(key) == eKeyHandled)
2133
0
      return eKeyHandled;
2134
2135
0
    if (!m_key_field.FieldDelegateOnLastOrOnlyElement())
2136
0
      return eKeyNotHandled;
2137
2138
0
    m_key_field.FieldDelegateExitCallback();
2139
0
    m_selection_type = SelectionType::Value;
2140
0
    m_value_field.FieldDelegateSelectFirstElement();
2141
0
    return eKeyHandled;
2142
0
  }
2143
2144
0
  HandleCharResult FieldDelegateHandleChar(int key) override {
2145
0
    switch (key) {
2146
0
    case KEY_RETURN:
2147
0
      return SelectNextField(key);
2148
0
    case '\t':
2149
0
      return SelectNext(key);
2150
0
    case KEY_SHIFT_TAB:
2151
0
      return SelectPrevious(key);
2152
0
    default:
2153
0
      break;
2154
0
    }
2155
2156
    // If the key wasn't handled, pass the key to the selected field.
2157
0
    if (m_selection_type == SelectionType::Key)
2158
0
      return m_key_field.FieldDelegateHandleChar(key);
2159
0
    else
2160
0
      return m_value_field.FieldDelegateHandleChar(key);
2161
2162
0
    return eKeyNotHandled;
2163
0
  }
2164
2165
0
  bool FieldDelegateOnFirstOrOnlyElement() override {
2166
0
    return m_selection_type == SelectionType::Key;
2167
0
  }
2168
2169
0
  bool FieldDelegateOnLastOrOnlyElement() override {
2170
0
    return m_selection_type == SelectionType::Value;
2171
0
  }
2172
2173
0
  void FieldDelegateSelectFirstElement() override {
2174
0
    m_selection_type = SelectionType::Key;
2175
0
  }
2176
2177
0
  void FieldDelegateSelectLastElement() override {
2178
0
    m_selection_type = SelectionType::Value;
2179
0
  }
2180
2181
0
  bool FieldDelegateHasError() override {
2182
0
    return m_key_field.FieldDelegateHasError() ||
2183
0
           m_value_field.FieldDelegateHasError();
2184
0
  }
2185
2186
0
  KeyFieldDelegateType &GetKeyField() { return m_key_field; }
2187
2188
0
  ValueFieldDelegateType &GetValueField() { return m_value_field; }
2189
2190
protected:
2191
  KeyFieldDelegateType m_key_field;
2192
  ValueFieldDelegateType m_value_field;
2193
  // See SelectionType class enum.
2194
  SelectionType m_selection_type;
2195
};
2196
2197
class EnvironmentVariableNameFieldDelegate : public TextFieldDelegate {
2198
public:
2199
  EnvironmentVariableNameFieldDelegate(const char *content)
2200
0
      : TextFieldDelegate("Name", content, true) {}
2201
2202
  // Environment variable names can't contain an equal sign.
2203
0
  bool IsAcceptableChar(int key) override {
2204
0
    return TextFieldDelegate::IsAcceptableChar(key) && key != '=';
2205
0
  }
2206
2207
0
  const std::string &GetName() { return m_content; }
2208
};
2209
2210
class EnvironmentVariableFieldDelegate
2211
    : public MappingFieldDelegate<EnvironmentVariableNameFieldDelegate,
2212
                                  TextFieldDelegate> {
2213
public:
2214
  EnvironmentVariableFieldDelegate()
2215
      : MappingFieldDelegate(
2216
            EnvironmentVariableNameFieldDelegate(""),
2217
0
            TextFieldDelegate("Value", "", /*required=*/false)) {}
2218
2219
0
  const std::string &GetName() { return GetKeyField().GetName(); }
2220
2221
0
  const std::string &GetValue() { return GetValueField().GetText(); }
2222
2223
0
  void SetName(const char *name) { return GetKeyField().SetText(name); }
2224
2225
0
  void SetValue(const char *value) { return GetValueField().SetText(value); }
2226
};
2227
2228
class EnvironmentVariableListFieldDelegate
2229
    : public ListFieldDelegate<EnvironmentVariableFieldDelegate> {
2230
public:
2231
  EnvironmentVariableListFieldDelegate(const char *label)
2232
0
      : ListFieldDelegate(label, EnvironmentVariableFieldDelegate()) {}
2233
2234
0
  Environment GetEnvironment() {
2235
0
    Environment environment;
2236
0
    for (int i = 0; i < GetNumberOfFields(); i++) {
2237
0
      environment.insert(
2238
0
          std::make_pair(GetField(i).GetName(), GetField(i).GetValue()));
2239
0
    }
2240
0
    return environment;
2241
0
  }
2242
2243
0
  void AddEnvironmentVariables(const Environment &environment) {
2244
0
    for (auto &variable : environment) {
2245
0
      AddNewField();
2246
0
      EnvironmentVariableFieldDelegate &field =
2247
0
          GetField(GetNumberOfFields() - 1);
2248
0
      field.SetName(variable.getKey().str().c_str());
2249
0
      field.SetValue(variable.getValue().c_str());
2250
0
    }
2251
0
  }
2252
};
2253
2254
class FormAction {
2255
public:
2256
  FormAction(const char *label, std::function<void(Window &)> action)
2257
0
      : m_action(action) {
2258
0
    if (label)
2259
0
      m_label = label;
2260
0
  }
2261
2262
  // Draw a centered [Label].
2263
0
  void Draw(Surface &surface, bool is_selected) {
2264
0
    int x = (surface.GetWidth() - m_label.length()) / 2;
2265
0
    surface.MoveCursor(x, 0);
2266
0
    if (is_selected)
2267
0
      surface.AttributeOn(A_REVERSE);
2268
0
    surface.PutChar('[');
2269
0
    surface.PutCString(m_label.c_str());
2270
0
    surface.PutChar(']');
2271
0
    if (is_selected)
2272
0
      surface.AttributeOff(A_REVERSE);
2273
0
  }
2274
2275
0
  void Execute(Window &window) { m_action(window); }
2276
2277
0
  const std::string &GetLabel() { return m_label; }
2278
2279
protected:
2280
  std::string m_label;
2281
  std::function<void(Window &)> m_action;
2282
};
2283
2284
class FormDelegate {
2285
public:
2286
0
  FormDelegate() {}
2287
2288
0
  virtual ~FormDelegate() = default;
2289
2290
  virtual std::string GetName() = 0;
2291
2292
0
  virtual void UpdateFieldsVisibility() {}
2293
2294
0
  FieldDelegate *GetField(uint32_t field_index) {
2295
0
    if (field_index < m_fields.size())
2296
0
      return m_fields[field_index].get();
2297
0
    return nullptr;
2298
0
  }
2299
2300
0
  FormAction &GetAction(int action_index) { return m_actions[action_index]; }
2301
2302
0
  int GetNumberOfFields() { return m_fields.size(); }
2303
2304
0
  int GetNumberOfActions() { return m_actions.size(); }
2305
2306
0
  bool HasError() { return !m_error.empty(); }
2307
2308
0
  void ClearError() { m_error.clear(); }
2309
2310
0
  const std::string &GetError() { return m_error; }
2311
2312
0
  void SetError(const char *error) { m_error = error; }
2313
2314
  // If all fields are valid, true is returned. Otherwise, an error message is
2315
  // set and false is returned. This method is usually called at the start of an
2316
  // action that requires valid fields.
2317
0
  bool CheckFieldsValidity() {
2318
0
    for (int i = 0; i < GetNumberOfFields(); i++) {
2319
0
      GetField(i)->FieldDelegateExitCallback();
2320
0
      if (GetField(i)->FieldDelegateHasError()) {
2321
0
        SetError("Some fields are invalid!");
2322
0
        return false;
2323
0
      }
2324
0
    }
2325
0
    return true;
2326
0
  }
2327
2328
  // Factory methods to create and add fields of specific types.
2329
2330
  TextFieldDelegate *AddTextField(const char *label, const char *content,
2331
0
                                  bool required) {
2332
0
    TextFieldDelegate *delegate =
2333
0
        new TextFieldDelegate(label, content, required);
2334
0
    m_fields.push_back(FieldDelegateUP(delegate));
2335
0
    return delegate;
2336
0
  }
2337
2338
  FileFieldDelegate *AddFileField(const char *label, const char *content,
2339
0
                                  bool need_to_exist, bool required) {
2340
0
    FileFieldDelegate *delegate =
2341
0
        new FileFieldDelegate(label, content, need_to_exist, required);
2342
0
    m_fields.push_back(FieldDelegateUP(delegate));
2343
0
    return delegate;
2344
0
  }
2345
2346
  DirectoryFieldDelegate *AddDirectoryField(const char *label,
2347
                                            const char *content,
2348
0
                                            bool need_to_exist, bool required) {
2349
0
    DirectoryFieldDelegate *delegate =
2350
0
        new DirectoryFieldDelegate(label, content, need_to_exist, required);
2351
0
    m_fields.push_back(FieldDelegateUP(delegate));
2352
0
    return delegate;
2353
0
  }
2354
2355
  ArchFieldDelegate *AddArchField(const char *label, const char *content,
2356
0
                                  bool required) {
2357
0
    ArchFieldDelegate *delegate =
2358
0
        new ArchFieldDelegate(label, content, required);
2359
0
    m_fields.push_back(FieldDelegateUP(delegate));
2360
0
    return delegate;
2361
0
  }
2362
2363
  IntegerFieldDelegate *AddIntegerField(const char *label, int content,
2364
0
                                        bool required) {
2365
0
    IntegerFieldDelegate *delegate =
2366
0
        new IntegerFieldDelegate(label, content, required);
2367
0
    m_fields.push_back(FieldDelegateUP(delegate));
2368
0
    return delegate;
2369
0
  }
2370
2371
0
  BooleanFieldDelegate *AddBooleanField(const char *label, bool content) {
2372
0
    BooleanFieldDelegate *delegate = new BooleanFieldDelegate(label, content);
2373
0
    m_fields.push_back(FieldDelegateUP(delegate));
2374
0
    return delegate;
2375
0
  }
2376
2377
  LazyBooleanFieldDelegate *AddLazyBooleanField(const char *label,
2378
0
                                                const char *calculate_label) {
2379
0
    LazyBooleanFieldDelegate *delegate =
2380
0
        new LazyBooleanFieldDelegate(label, calculate_label);
2381
0
    m_fields.push_back(FieldDelegateUP(delegate));
2382
0
    return delegate;
2383
0
  }
2384
2385
  ChoicesFieldDelegate *AddChoicesField(const char *label, int height,
2386
0
                                        std::vector<std::string> choices) {
2387
0
    ChoicesFieldDelegate *delegate =
2388
0
        new ChoicesFieldDelegate(label, height, choices);
2389
0
    m_fields.push_back(FieldDelegateUP(delegate));
2390
0
    return delegate;
2391
0
  }
2392
2393
0
  PlatformPluginFieldDelegate *AddPlatformPluginField(Debugger &debugger) {
2394
0
    PlatformPluginFieldDelegate *delegate =
2395
0
        new PlatformPluginFieldDelegate(debugger);
2396
0
    m_fields.push_back(FieldDelegateUP(delegate));
2397
0
    return delegate;
2398
0
  }
2399
2400
0
  ProcessPluginFieldDelegate *AddProcessPluginField() {
2401
0
    ProcessPluginFieldDelegate *delegate = new ProcessPluginFieldDelegate();
2402
0
    m_fields.push_back(FieldDelegateUP(delegate));
2403
0
    return delegate;
2404
0
  }
2405
2406
  template <class T>
2407
  ListFieldDelegate<T> *AddListField(const char *label, T default_field) {
2408
    ListFieldDelegate<T> *delegate =
2409
        new ListFieldDelegate<T>(label, default_field);
2410
    m_fields.push_back(FieldDelegateUP(delegate));
2411
    return delegate;
2412
  }
2413
2414
0
  ArgumentsFieldDelegate *AddArgumentsField() {
2415
0
    ArgumentsFieldDelegate *delegate = new ArgumentsFieldDelegate();
2416
0
    m_fields.push_back(FieldDelegateUP(delegate));
2417
0
    return delegate;
2418
0
  }
2419
2420
  template <class K, class V>
2421
  MappingFieldDelegate<K, V> *AddMappingField(K key_field, V value_field) {
2422
    MappingFieldDelegate<K, V> *delegate =
2423
        new MappingFieldDelegate<K, V>(key_field, value_field);
2424
    m_fields.push_back(FieldDelegateUP(delegate));
2425
    return delegate;
2426
  }
2427
2428
  EnvironmentVariableNameFieldDelegate *
2429
0
  AddEnvironmentVariableNameField(const char *content) {
2430
0
    EnvironmentVariableNameFieldDelegate *delegate =
2431
0
        new EnvironmentVariableNameFieldDelegate(content);
2432
0
    m_fields.push_back(FieldDelegateUP(delegate));
2433
0
    return delegate;
2434
0
  }
2435
2436
0
  EnvironmentVariableFieldDelegate *AddEnvironmentVariableField() {
2437
0
    EnvironmentVariableFieldDelegate *delegate =
2438
0
        new EnvironmentVariableFieldDelegate();
2439
0
    m_fields.push_back(FieldDelegateUP(delegate));
2440
0
    return delegate;
2441
0
  }
2442
2443
  EnvironmentVariableListFieldDelegate *
2444
0
  AddEnvironmentVariableListField(const char *label) {
2445
0
    EnvironmentVariableListFieldDelegate *delegate =
2446
0
        new EnvironmentVariableListFieldDelegate(label);
2447
0
    m_fields.push_back(FieldDelegateUP(delegate));
2448
0
    return delegate;
2449
0
  }
2450
2451
  // Factory methods for adding actions.
2452
2453
0
  void AddAction(const char *label, std::function<void(Window &)> action) {
2454
0
    m_actions.push_back(FormAction(label, action));
2455
0
  }
2456
2457
protected:
2458
  std::vector<FieldDelegateUP> m_fields;
2459
  std::vector<FormAction> m_actions;
2460
  // Optional error message. If empty, form is considered to have no error.
2461
  std::string m_error;
2462
};
2463
2464
typedef std::shared_ptr<FormDelegate> FormDelegateSP;
2465
2466
class FormWindowDelegate : public WindowDelegate {
2467
public:
2468
  FormWindowDelegate(FormDelegateSP &delegate_sp)
2469
      : m_delegate_sp(delegate_sp), m_selection_index(0),
2470
0
        m_first_visible_line(0) {
2471
0
    assert(m_delegate_sp->GetNumberOfActions() > 0);
2472
0
    if (m_delegate_sp->GetNumberOfFields() > 0)
2473
0
      m_selection_type = SelectionType::Field;
2474
0
    else
2475
0
      m_selection_type = SelectionType::Action;
2476
0
  }
2477
2478
  // Signify which element is selected. If a field or an action is selected,
2479
  // then m_selection_index signifies the particular field or action that is
2480
  // selected.
2481
  enum class SelectionType { Field, Action };
2482
2483
  // A form window is padded by one character from all sides. First, if an error
2484
  // message exists, it is drawn followed by a separator. Then one or more
2485
  // fields are drawn. Finally, all available actions are drawn on a single
2486
  // line.
2487
  //
2488
  // ___<Form Name>_________________________________________________
2489
  // |                                                             |
2490
  // | - Error message if it exists.                               |
2491
  // |-------------------------------------------------------------|
2492
  // | Form elements here.                                         |
2493
  // |                       Form actions here.                    |
2494
  // |                                                             |
2495
  // |______________________________________[Press Esc to cancel]__|
2496
  //
2497
2498
  // One line for the error and another for the horizontal line.
2499
0
  int GetErrorHeight() {
2500
0
    if (m_delegate_sp->HasError())
2501
0
      return 2;
2502
0
    return 0;
2503
0
  }
2504
2505
  // Actions span a single line.
2506
0
  int GetActionsHeight() {
2507
0
    if (m_delegate_sp->GetNumberOfActions() > 0)
2508
0
      return 1;
2509
0
    return 0;
2510
0
  }
2511
2512
  // Get the total number of needed lines to draw the contents.
2513
0
  int GetContentHeight() {
2514
0
    int height = 0;
2515
0
    height += GetErrorHeight();
2516
0
    for (int i = 0; i < m_delegate_sp->GetNumberOfFields(); i++) {
2517
0
      if (!m_delegate_sp->GetField(i)->FieldDelegateIsVisible())
2518
0
        continue;
2519
0
      height += m_delegate_sp->GetField(i)->FieldDelegateGetHeight();
2520
0
    }
2521
0
    height += GetActionsHeight();
2522
0
    return height;
2523
0
  }
2524
2525
0
  ScrollContext GetScrollContext() {
2526
0
    if (m_selection_type == SelectionType::Action)
2527
0
      return ScrollContext(GetContentHeight() - 1);
2528
2529
0
    FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);
2530
0
    ScrollContext context = field->FieldDelegateGetScrollContext();
2531
2532
0
    int offset = GetErrorHeight();
2533
0
    for (int i = 0; i < m_selection_index; i++) {
2534
0
      if (!m_delegate_sp->GetField(i)->FieldDelegateIsVisible())
2535
0
        continue;
2536
0
      offset += m_delegate_sp->GetField(i)->FieldDelegateGetHeight();
2537
0
    }
2538
0
    context.Offset(offset);
2539
2540
    // If the context is touching the error, include the error in the context as
2541
    // well.
2542
0
    if (context.start == GetErrorHeight())
2543
0
      context.start = 0;
2544
2545
0
    return context;
2546
0
  }
2547
2548
0
  void UpdateScrolling(Surface &surface) {
2549
0
    ScrollContext context = GetScrollContext();
2550
0
    int content_height = GetContentHeight();
2551
0
    int surface_height = surface.GetHeight();
2552
0
    int visible_height = std::min(content_height, surface_height);
2553
0
    int last_visible_line = m_first_visible_line + visible_height - 1;
2554
2555
    // If the last visible line is bigger than the content, then it is invalid
2556
    // and needs to be set to the last line in the content. This can happen when
2557
    // a field has shrunk in height.
2558
0
    if (last_visible_line > content_height - 1) {
2559
0
      m_first_visible_line = content_height - visible_height;
2560
0
    }
2561
2562
0
    if (context.start < m_first_visible_line) {
2563
0
      m_first_visible_line = context.start;
2564
0
      return;
2565
0
    }
2566
2567
0
    if (context.end > last_visible_line) {
2568
0
      m_first_visible_line = context.end - visible_height + 1;
2569
0
    }
2570
0
  }
2571
2572
0
  void DrawError(Surface &surface) {
2573
0
    if (!m_delegate_sp->HasError())
2574
0
      return;
2575
0
    surface.MoveCursor(0, 0);
2576
0
    surface.AttributeOn(COLOR_PAIR(RedOnBlack));
2577
0
    surface.PutChar(ACS_DIAMOND);
2578
0
    surface.PutChar(' ');
2579
0
    surface.PutCStringTruncated(1, m_delegate_sp->GetError().c_str());
2580
0
    surface.AttributeOff(COLOR_PAIR(RedOnBlack));
2581
2582
0
    surface.MoveCursor(0, 1);
2583
0
    surface.HorizontalLine(surface.GetWidth());
2584
0
  }
2585
2586
0
  void DrawFields(Surface &surface) {
2587
0
    int line = 0;
2588
0
    int width = surface.GetWidth();
2589
0
    bool a_field_is_selected = m_selection_type == SelectionType::Field;
2590
0
    for (int i = 0; i < m_delegate_sp->GetNumberOfFields(); i++) {
2591
0
      FieldDelegate *field = m_delegate_sp->GetField(i);
2592
0
      if (!field->FieldDelegateIsVisible())
2593
0
        continue;
2594
0
      bool is_field_selected = a_field_is_selected && m_selection_index == i;
2595
0
      int height = field->FieldDelegateGetHeight();
2596
0
      Rect bounds = Rect(Point(0, line), Size(width, height));
2597
0
      Surface field_surface = surface.SubSurface(bounds);
2598
0
      field->FieldDelegateDraw(field_surface, is_field_selected);
2599
0
      line += height;
2600
0
    }
2601
0
  }
2602
2603
0
  void DrawActions(Surface &surface) {
2604
0
    int number_of_actions = m_delegate_sp->GetNumberOfActions();
2605
0
    int width = surface.GetWidth() / number_of_actions;
2606
0
    bool an_action_is_selected = m_selection_type == SelectionType::Action;
2607
0
    int x = 0;
2608
0
    for (int i = 0; i < number_of_actions; i++) {
2609
0
      bool is_action_selected = an_action_is_selected && m_selection_index == i;
2610
0
      FormAction &action = m_delegate_sp->GetAction(i);
2611
0
      Rect bounds = Rect(Point(x, 0), Size(width, 1));
2612
0
      Surface action_surface = surface.SubSurface(bounds);
2613
0
      action.Draw(action_surface, is_action_selected);
2614
0
      x += width;
2615
0
    }
2616
0
  }
2617
2618
0
  void DrawElements(Surface &surface) {
2619
0
    Rect frame = surface.GetFrame();
2620
0
    Rect fields_bounds, actions_bounds;
2621
0
    frame.HorizontalSplit(surface.GetHeight() - GetActionsHeight(),
2622
0
                          fields_bounds, actions_bounds);
2623
0
    Surface fields_surface = surface.SubSurface(fields_bounds);
2624
0
    Surface actions_surface = surface.SubSurface(actions_bounds);
2625
2626
0
    DrawFields(fields_surface);
2627
0
    DrawActions(actions_surface);
2628
0
  }
2629
2630
  // Contents are first drawn on a pad. Then a subset of that pad is copied to
2631
  // the derived window starting at the first visible line. This essentially
2632
  // provides scrolling functionality.
2633
0
  void DrawContent(Surface &surface) {
2634
0
    UpdateScrolling(surface);
2635
2636
0
    int width = surface.GetWidth();
2637
0
    int height = GetContentHeight();
2638
0
    Pad pad = Pad(Size(width, height));
2639
2640
0
    Rect frame = pad.GetFrame();
2641
0
    Rect error_bounds, elements_bounds;
2642
0
    frame.HorizontalSplit(GetErrorHeight(), error_bounds, elements_bounds);
2643
0
    Surface error_surface = pad.SubSurface(error_bounds);
2644
0
    Surface elements_surface = pad.SubSurface(elements_bounds);
2645
2646
0
    DrawError(error_surface);
2647
0
    DrawElements(elements_surface);
2648
2649
0
    int copy_height = std::min(surface.GetHeight(), pad.GetHeight());
2650
0
    pad.CopyToSurface(surface, Point(0, m_first_visible_line), Point(),
2651
0
                      Size(width, copy_height));
2652
0
  }
2653
2654
0
  void DrawSubmitHint(Surface &surface, bool is_active) {
2655
0
    surface.MoveCursor(2, surface.GetHeight() - 1);
2656
0
    if (is_active)
2657
0
      surface.AttributeOn(A_BOLD | COLOR_PAIR(BlackOnWhite));
2658
0
    surface.Printf("[Press Alt+Enter to %s]",
2659
0
                   m_delegate_sp->GetAction(0).GetLabel().c_str());
2660
0
    if (is_active)
2661
0
      surface.AttributeOff(A_BOLD | COLOR_PAIR(BlackOnWhite));
2662
0
  }
2663
2664
0
  bool WindowDelegateDraw(Window &window, bool force) override {
2665
0
    m_delegate_sp->UpdateFieldsVisibility();
2666
2667
0
    window.Erase();
2668
2669
0
    window.DrawTitleBox(m_delegate_sp->GetName().c_str(),
2670
0
                        "Press Esc to Cancel");
2671
0
    DrawSubmitHint(window, window.IsActive());
2672
2673
0
    Rect content_bounds = window.GetFrame();
2674
0
    content_bounds.Inset(2, 2);
2675
0
    Surface content_surface = window.SubSurface(content_bounds);
2676
2677
0
    DrawContent(content_surface);
2678
0
    return true;
2679
0
  }
2680
2681
0
  void SkipNextHiddenFields() {
2682
0
    while (true) {
2683
0
      if (m_delegate_sp->GetField(m_selection_index)->FieldDelegateIsVisible())
2684
0
        return;
2685
2686
0
      if (m_selection_index == m_delegate_sp->GetNumberOfFields() - 1) {
2687
0
        m_selection_type = SelectionType::Action;
2688
0
        m_selection_index = 0;
2689
0
        return;
2690
0
      }
2691
2692
0
      m_selection_index++;
2693
0
    }
2694
0
  }
2695
2696
0
  HandleCharResult SelectNext(int key) {
2697
0
    if (m_selection_type == SelectionType::Action) {
2698
0
      if (m_selection_index < m_delegate_sp->GetNumberOfActions() - 1) {
2699
0
        m_selection_index++;
2700
0
        return eKeyHandled;
2701
0
      }
2702
2703
0
      m_selection_index = 0;
2704
0
      m_selection_type = SelectionType::Field;
2705
0
      SkipNextHiddenFields();
2706
0
      if (m_selection_type == SelectionType::Field) {
2707
0
        FieldDelegate *next_field = m_delegate_sp->GetField(m_selection_index);
2708
0
        next_field->FieldDelegateSelectFirstElement();
2709
0
      }
2710
0
      return eKeyHandled;
2711
0
    }
2712
2713
0
    FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);
2714
0
    if (!field->FieldDelegateOnLastOrOnlyElement()) {
2715
0
      return field->FieldDelegateHandleChar(key);
2716
0
    }
2717
2718
0
    field->FieldDelegateExitCallback();
2719
2720
0
    if (m_selection_index == m_delegate_sp->GetNumberOfFields() - 1) {
2721
0
      m_selection_type = SelectionType::Action;
2722
0
      m_selection_index = 0;
2723
0
      return eKeyHandled;
2724
0
    }
2725
2726
0
    m_selection_index++;
2727
0
    SkipNextHiddenFields();
2728
2729
0
    if (m_selection_type == SelectionType::Field) {
2730
0
      FieldDelegate *next_field = m_delegate_sp->GetField(m_selection_index);
2731
0
      next_field->FieldDelegateSelectFirstElement();
2732
0
    }
2733
2734
0
    return eKeyHandled;
2735
0
  }
2736
2737
0
  void SkipPreviousHiddenFields() {
2738
0
    while (true) {
2739
0
      if (m_delegate_sp->GetField(m_selection_index)->FieldDelegateIsVisible())
2740
0
        return;
2741
2742
0
      if (m_selection_index == 0) {
2743
0
        m_selection_type = SelectionType::Action;
2744
0
        m_selection_index = 0;
2745
0
        return;
2746
0
      }
2747
2748
0
      m_selection_index--;
2749
0
    }
2750
0
  }
2751
2752
0
  HandleCharResult SelectPrevious(int key) {
2753
0
    if (m_selection_type == SelectionType::Action) {
2754
0
      if (m_selection_index > 0) {
2755
0
        m_selection_index--;
2756
0
        return eKeyHandled;
2757
0
      }
2758
0
      m_selection_index = m_delegate_sp->GetNumberOfFields() - 1;
2759
0
      m_selection_type = SelectionType::Field;
2760
0
      SkipPreviousHiddenFields();
2761
0
      if (m_selection_type == SelectionType::Field) {
2762
0
        FieldDelegate *previous_field =
2763
0
            m_delegate_sp->GetField(m_selection_index);
2764
0
        previous_field->FieldDelegateSelectLastElement();
2765
0
      }
2766
0
      return eKeyHandled;
2767
0
    }
2768
2769
0
    FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);
2770
0
    if (!field->FieldDelegateOnFirstOrOnlyElement()) {
2771
0
      return field->FieldDelegateHandleChar(key);
2772
0
    }
2773
2774
0
    field->FieldDelegateExitCallback();
2775
2776
0
    if (m_selection_index == 0) {
2777
0
      m_selection_type = SelectionType::Action;
2778
0
      m_selection_index = m_delegate_sp->GetNumberOfActions() - 1;
2779
0
      return eKeyHandled;
2780
0
    }
2781
2782
0
    m_selection_index--;
2783
0
    SkipPreviousHiddenFields();
2784
2785
0
    if (m_selection_type == SelectionType::Field) {
2786
0
      FieldDelegate *previous_field =
2787
0
          m_delegate_sp->GetField(m_selection_index);
2788
0
      previous_field->FieldDelegateSelectLastElement();
2789
0
    }
2790
2791
0
    return eKeyHandled;
2792
0
  }
2793
2794
0
  void ExecuteAction(Window &window, int index) {
2795
0
    FormAction &action = m_delegate_sp->GetAction(index);
2796
0
    action.Execute(window);
2797
0
    if (m_delegate_sp->HasError()) {
2798
0
      m_first_visible_line = 0;
2799
0
      m_selection_index = 0;
2800
0
      m_selection_type = SelectionType::Field;
2801
0
    }
2802
0
  }
2803
2804
  // Always return eKeyHandled to absorb all events since forms are always
2805
  // added as pop-ups that should take full control until canceled or submitted.
2806
0
  HandleCharResult WindowDelegateHandleChar(Window &window, int key) override {
2807
0
    switch (key) {
2808
0
    case '\r':
2809
0
    case '\n':
2810
0
    case KEY_ENTER:
2811
0
      if (m_selection_type == SelectionType::Action) {
2812
0
        ExecuteAction(window, m_selection_index);
2813
0
        return eKeyHandled;
2814
0
      }
2815
0
      break;
2816
0
    case KEY_ALT_ENTER:
2817
0
      ExecuteAction(window, 0);
2818
0
      return eKeyHandled;
2819
0
    case '\t':
2820
0
      SelectNext(key);
2821
0
      return eKeyHandled;
2822
0
    case KEY_SHIFT_TAB:
2823
0
      SelectPrevious(key);
2824
0
      return eKeyHandled;
2825
0
    case KEY_ESCAPE:
2826
0
      window.GetParent()->RemoveSubWindow(&window);
2827
0
      return eKeyHandled;
2828
0
    default:
2829
0
      break;
2830
0
    }
2831
2832
    // If the key wasn't handled and one of the fields is selected, pass the key
2833
    // to that field.
2834
0
    if (m_selection_type == SelectionType::Field) {
2835
0
      FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);
2836
0
      if (field->FieldDelegateHandleChar(key) == eKeyHandled)
2837
0
        return eKeyHandled;
2838
0
    }
2839
2840
    // If the key wasn't handled by the possibly selected field, handle some
2841
    // extra keys for navigation.
2842
0
    switch (key) {
2843
0
    case KEY_DOWN:
2844
0
      SelectNext(key);
2845
0
      return eKeyHandled;
2846
0
    case KEY_UP:
2847
0
      SelectPrevious(key);
2848
0
      return eKeyHandled;
2849
0
    default:
2850
0
      break;
2851
0
    }
2852
2853
0
    return eKeyHandled;
2854
0
  }
2855
2856
protected:
2857
  FormDelegateSP m_delegate_sp;
2858
  // The index of the currently selected SelectionType.
2859
  int m_selection_index;
2860
  // See SelectionType class enum.
2861
  SelectionType m_selection_type;
2862
  // The first visible line from the pad.
2863
  int m_first_visible_line;
2864
};
2865
2866
///////////////////////////
2867
// Form Delegate Instances
2868
///////////////////////////
2869
2870
class DetachOrKillProcessFormDelegate : public FormDelegate {
2871
public:
2872
0
  DetachOrKillProcessFormDelegate(Process *process) : m_process(process) {
2873
0
    SetError("There is a running process, either detach or kill it.");
2874
2875
0
    m_keep_stopped_field =
2876
0
        AddBooleanField("Keep process stopped when detaching.", false);
2877
2878
0
    AddAction("Detach", [this](Window &window) { Detach(window); });
2879
0
    AddAction("Kill", [this](Window &window) { Kill(window); });
2880
0
  }
2881
2882
0
  std::string GetName() override { return "Detach/Kill Process"; }
2883
2884
0
  void Kill(Window &window) {
2885
0
    Status destroy_status(m_process->Destroy(false));
2886
0
    if (destroy_status.Fail()) {
2887
0
      SetError("Failed to kill process.");
2888
0
      return;
2889
0
    }
2890
0
    window.GetParent()->RemoveSubWindow(&window);
2891
0
  }
2892
2893
0
  void Detach(Window &window) {
2894
0
    Status detach_status(m_process->Detach(m_keep_stopped_field->GetBoolean()));
2895
0
    if (detach_status.Fail()) {
2896
0
      SetError("Failed to detach from process.");
2897
0
      return;
2898
0
    }
2899
0
    window.GetParent()->RemoveSubWindow(&window);
2900
0
  }
2901
2902
protected:
2903
  Process *m_process;
2904
  BooleanFieldDelegate *m_keep_stopped_field;
2905
};
2906
2907
class ProcessAttachFormDelegate : public FormDelegate {
2908
public:
2909
  ProcessAttachFormDelegate(Debugger &debugger, WindowSP main_window_sp)
2910
0
      : m_debugger(debugger), m_main_window_sp(main_window_sp) {
2911
0
    std::vector<std::string> types;
2912
0
    types.push_back(std::string("Name"));
2913
0
    types.push_back(std::string("PID"));
2914
0
    m_type_field = AddChoicesField("Attach By", 2, types);
2915
0
    m_pid_field = AddIntegerField("PID", 0, true);
2916
0
    m_name_field =
2917
0
        AddTextField("Process Name", GetDefaultProcessName().c_str(), true);
2918
0
    m_continue_field = AddBooleanField("Continue once attached.", false);
2919
0
    m_wait_for_field = AddBooleanField("Wait for process to launch.", false);
2920
0
    m_include_existing_field =
2921
0
        AddBooleanField("Include existing processes.", false);
2922
0
    m_show_advanced_field = AddBooleanField("Show advanced settings.", false);
2923
0
    m_plugin_field = AddProcessPluginField();
2924
2925
0
    AddAction("Attach", [this](Window &window) { Attach(window); });
2926
0
  }
2927
2928
0
  std::string GetName() override { return "Attach Process"; }
2929
2930
0
  void UpdateFieldsVisibility() override {
2931
0
    if (m_type_field->GetChoiceContent() == "Name") {
2932
0
      m_pid_field->FieldDelegateHide();
2933
0
      m_name_field->FieldDelegateShow();
2934
0
      m_wait_for_field->FieldDelegateShow();
2935
0
      if (m_wait_for_field->GetBoolean())
2936
0
        m_include_existing_field->FieldDelegateShow();
2937
0
      else
2938
0
        m_include_existing_field->FieldDelegateHide();
2939
0
    } else {
2940
0
      m_pid_field->FieldDelegateShow();
2941
0
      m_name_field->FieldDelegateHide();
2942
0
      m_wait_for_field->FieldDelegateHide();
2943
0
      m_include_existing_field->FieldDelegateHide();
2944
0
    }
2945
0
    if (m_show_advanced_field->GetBoolean())
2946
0
      m_plugin_field->FieldDelegateShow();
2947
0
    else
2948
0
      m_plugin_field->FieldDelegateHide();
2949
0
  }
2950
2951
  // Get the basename of the target's main executable if available, empty string
2952
  // otherwise.
2953
0
  std::string GetDefaultProcessName() {
2954
0
    Target *target = m_debugger.GetSelectedTarget().get();
2955
0
    if (target == nullptr)
2956
0
      return "";
2957
2958
0
    ModuleSP module_sp = target->GetExecutableModule();
2959
0
    if (!module_sp->IsExecutable())
2960
0
      return "";
2961
2962
0
    return module_sp->GetFileSpec().GetFilename().AsCString();
2963
0
  }
2964
2965
0
  bool StopRunningProcess() {
2966
0
    ExecutionContext exe_ctx =
2967
0
        m_debugger.GetCommandInterpreter().GetExecutionContext();
2968
2969
0
    if (!exe_ctx.HasProcessScope())
2970
0
      return false;
2971
2972
0
    Process *process = exe_ctx.GetProcessPtr();
2973
0
    if (!(process && process->IsAlive()))
2974
0
      return false;
2975
2976
0
    FormDelegateSP form_delegate_sp =
2977
0
        FormDelegateSP(new DetachOrKillProcessFormDelegate(process));
2978
0
    Rect bounds = m_main_window_sp->GetCenteredRect(85, 8);
2979
0
    WindowSP form_window_sp = m_main_window_sp->CreateSubWindow(
2980
0
        form_delegate_sp->GetName().c_str(), bounds, true);
2981
0
    WindowDelegateSP window_delegate_sp =
2982
0
        WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));
2983
0
    form_window_sp->SetDelegate(window_delegate_sp);
2984
2985
0
    return true;
2986
0
  }
2987
2988
0
  Target *GetTarget() {
2989
0
    Target *target = m_debugger.GetSelectedTarget().get();
2990
2991
0
    if (target != nullptr)
2992
0
      return target;
2993
2994
0
    TargetSP new_target_sp;
2995
0
    m_debugger.GetTargetList().CreateTarget(
2996
0
        m_debugger, "", "", eLoadDependentsNo, nullptr, new_target_sp);
2997
2998
0
    target = new_target_sp.get();
2999
3000
0
    if (target == nullptr)
3001
0
      SetError("Failed to create target.");
3002
3003
0
    m_debugger.GetTargetList().SetSelectedTarget(new_target_sp);
3004
3005
0
    return target;
3006
0
  }
3007
3008
0
  ProcessAttachInfo GetAttachInfo() {
3009
0
    ProcessAttachInfo attach_info;
3010
0
    attach_info.SetContinueOnceAttached(m_continue_field->GetBoolean());
3011
0
    if (m_type_field->GetChoiceContent() == "Name") {
3012
0
      attach_info.GetExecutableFile().SetFile(m_name_field->GetText(),
3013
0
                                              FileSpec::Style::native);
3014
0
      attach_info.SetWaitForLaunch(m_wait_for_field->GetBoolean());
3015
0
      if (m_wait_for_field->GetBoolean())
3016
0
        attach_info.SetIgnoreExisting(!m_include_existing_field->GetBoolean());
3017
0
    } else {
3018
0
      attach_info.SetProcessID(m_pid_field->GetInteger());
3019
0
    }
3020
0
    attach_info.SetProcessPluginName(m_plugin_field->GetPluginName());
3021
3022
0
    return attach_info;
3023
0
  }
3024
3025
0
  void Attach(Window &window) {
3026
0
    ClearError();
3027
3028
0
    bool all_fields_are_valid = CheckFieldsValidity();
3029
0
    if (!all_fields_are_valid)
3030
0
      return;
3031
3032
0
    bool process_is_running = StopRunningProcess();
3033
0
    if (process_is_running)
3034
0
      return;
3035
3036
0
    Target *target = GetTarget();
3037
0
    if (HasError())
3038
0
      return;
3039
3040
0
    StreamString stream;
3041
0
    ProcessAttachInfo attach_info = GetAttachInfo();
3042
0
    Status status = target->Attach(attach_info, &stream);
3043
3044
0
    if (status.Fail()) {
3045
0
      SetError(status.AsCString());
3046
0
      return;
3047
0
    }
3048
3049
0
    ProcessSP process_sp(target->GetProcessSP());
3050
0
    if (!process_sp) {
3051
0
      SetError("Attached sucessfully but target has no process.");
3052
0
      return;
3053
0
    }
3054
3055
0
    if (attach_info.GetContinueOnceAttached())
3056
0
      process_sp->Resume();
3057
3058
0
    window.GetParent()->RemoveSubWindow(&window);
3059
0
  }
3060
3061
protected:
3062
  Debugger &m_debugger;
3063
  WindowSP m_main_window_sp;
3064
3065
  ChoicesFieldDelegate *m_type_field;
3066
  IntegerFieldDelegate *m_pid_field;
3067
  TextFieldDelegate *m_name_field;
3068
  BooleanFieldDelegate *m_continue_field;
3069
  BooleanFieldDelegate *m_wait_for_field;
3070
  BooleanFieldDelegate *m_include_existing_field;
3071
  BooleanFieldDelegate *m_show_advanced_field;
3072
  ProcessPluginFieldDelegate *m_plugin_field;
3073
};
3074
3075
class TargetCreateFormDelegate : public FormDelegate {
3076
public:
3077
0
  TargetCreateFormDelegate(Debugger &debugger) : m_debugger(debugger) {
3078
0
    m_executable_field = AddFileField("Executable", "", /*need_to_exist=*/true,
3079
0
                                      /*required=*/true);
3080
0
    m_core_file_field = AddFileField("Core File", "", /*need_to_exist=*/true,
3081
0
                                     /*required=*/false);
3082
0
    m_symbol_file_field = AddFileField(
3083
0
        "Symbol File", "", /*need_to_exist=*/true, /*required=*/false);
3084
0
    m_show_advanced_field = AddBooleanField("Show advanced settings.", false);
3085
0
    m_remote_file_field = AddFileField(
3086
0
        "Remote File", "", /*need_to_exist=*/false, /*required=*/false);
3087
0
    m_arch_field = AddArchField("Architecture", "", /*required=*/false);
3088
0
    m_platform_field = AddPlatformPluginField(debugger);
3089
0
    m_load_dependent_files_field =
3090
0
        AddChoicesField("Load Dependents", 3, GetLoadDependentFilesChoices());
3091
3092
0
    AddAction("Create", [this](Window &window) { CreateTarget(window); });
3093
0
  }
3094
3095
0
  std::string GetName() override { return "Create Target"; }
3096
3097
0
  void UpdateFieldsVisibility() override {
3098
0
    if (m_show_advanced_field->GetBoolean()) {
3099
0
      m_remote_file_field->FieldDelegateShow();
3100
0
      m_arch_field->FieldDelegateShow();
3101
0
      m_platform_field->FieldDelegateShow();
3102
0
      m_load_dependent_files_field->FieldDelegateShow();
3103
0
    } else {
3104
0
      m_remote_file_field->FieldDelegateHide();
3105
0
      m_arch_field->FieldDelegateHide();
3106
0
      m_platform_field->FieldDelegateHide();
3107
0
      m_load_dependent_files_field->FieldDelegateHide();
3108
0
    }
3109
0
  }
3110
3111
  static constexpr const char *kLoadDependentFilesNo = "No";
3112
  static constexpr const char *kLoadDependentFilesYes = "Yes";
3113
  static constexpr const char *kLoadDependentFilesExecOnly = "Executable only";
3114
3115
0
  std::vector<std::string> GetLoadDependentFilesChoices() {
3116
0
    std::vector<std::string> load_depentents_options;
3117
0
    load_depentents_options.push_back(kLoadDependentFilesExecOnly);
3118
0
    load_depentents_options.push_back(kLoadDependentFilesYes);
3119
0
    load_depentents_options.push_back(kLoadDependentFilesNo);
3120
0
    return load_depentents_options;
3121
0
  }
3122
3123
0
  LoadDependentFiles GetLoadDependentFiles() {
3124
0
    std::string choice = m_load_dependent_files_field->GetChoiceContent();
3125
0
    if (choice == kLoadDependentFilesNo)
3126
0
      return eLoadDependentsNo;
3127
0
    if (choice == kLoadDependentFilesYes)
3128
0
      return eLoadDependentsYes;
3129
0
    return eLoadDependentsDefault;
3130
0
  }
3131
3132
0
  OptionGroupPlatform GetPlatformOptions() {
3133
0
    OptionGroupPlatform platform_options(false);
3134
0
    platform_options.SetPlatformName(m_platform_field->GetPluginName().c_str());
3135
0
    return platform_options;
3136
0
  }
3137
3138
0
  TargetSP GetTarget() {
3139
0
    OptionGroupPlatform platform_options = GetPlatformOptions();
3140
0
    TargetSP target_sp;
3141
0
    Status status = m_debugger.GetTargetList().CreateTarget(
3142
0
        m_debugger, m_executable_field->GetPath(),
3143
0
        m_arch_field->GetArchString(), GetLoadDependentFiles(),
3144
0
        &platform_options, target_sp);
3145
3146
0
    if (status.Fail()) {
3147
0
      SetError(status.AsCString());
3148
0
      return nullptr;
3149
0
    }
3150
3151
0
    m_debugger.GetTargetList().SetSelectedTarget(target_sp);
3152
3153
0
    return target_sp;
3154
0
  }
3155
3156
0
  void SetSymbolFile(TargetSP target_sp) {
3157
0
    if (!m_symbol_file_field->IsSpecified())
3158
0
      return;
3159
3160
0
    ModuleSP module_sp(target_sp->GetExecutableModule());
3161
0
    if (!module_sp)
3162
0
      return;
3163
3164
0
    module_sp->SetSymbolFileFileSpec(
3165
0
        m_symbol_file_field->GetResolvedFileSpec());
3166
0
  }
3167
3168
0
  void SetCoreFile(TargetSP target_sp) {
3169
0
    if (!m_core_file_field->IsSpecified())
3170
0
      return;
3171
3172
0
    FileSpec core_file_spec = m_core_file_field->GetResolvedFileSpec();
3173
3174
0
    FileSpec core_file_directory_spec;
3175
0
    core_file_directory_spec.GetDirectory() = core_file_spec.GetDirectory();
3176
0
    target_sp->AppendExecutableSearchPaths(core_file_directory_spec);
3177
3178
0
    ProcessSP process_sp(target_sp->CreateProcess(
3179
0
        m_debugger.GetListener(), llvm::StringRef(), &core_file_spec, false));
3180
3181
0
    if (!process_sp) {
3182
0
      SetError("Unable to find process plug-in for core file!");
3183
0
      return;
3184
0
    }
3185
3186
0
    Status status = process_sp->LoadCore();
3187
0
    if (status.Fail()) {
3188
0
      SetError("Can't find plug-in for core file!");
3189
0
      return;
3190
0
    }
3191
0
  }
3192
3193
0
  void SetRemoteFile(TargetSP target_sp) {
3194
0
    if (!m_remote_file_field->IsSpecified())
3195
0
      return;
3196
3197
0
    ModuleSP module_sp(target_sp->GetExecutableModule());
3198
0
    if (!module_sp)
3199
0
      return;
3200
3201
0
    FileSpec remote_file_spec = m_remote_file_field->GetFileSpec();
3202
0
    module_sp->SetPlatformFileSpec(remote_file_spec);
3203
0
  }
3204
3205
0
  void RemoveTarget(TargetSP target_sp) {
3206
0
    m_debugger.GetTargetList().DeleteTarget(target_sp);
3207
0
  }
3208
3209
0
  void CreateTarget(Window &window) {
3210
0
    ClearError();
3211
3212
0
    bool all_fields_are_valid = CheckFieldsValidity();
3213
0
    if (!all_fields_are_valid)
3214
0
      return;
3215
3216
0
    TargetSP target_sp = GetTarget();
3217
0
    if (HasError())
3218
0
      return;
3219
3220
0
    SetSymbolFile(target_sp);
3221
0
    if (HasError()) {
3222
0
      RemoveTarget(target_sp);
3223
0
      return;
3224
0
    }
3225
3226
0
    SetCoreFile(target_sp);
3227
0
    if (HasError()) {
3228
0
      RemoveTarget(target_sp);
3229
0
      return;
3230
0
    }
3231
3232
0
    SetRemoteFile(target_sp);
3233
0
    if (HasError()) {
3234
0
      RemoveTarget(target_sp);
3235
0
      return;
3236
0
    }
3237
3238
0
    window.GetParent()->RemoveSubWindow(&window);
3239
0
  }
3240
3241
protected:
3242
  Debugger &m_debugger;
3243
3244
  FileFieldDelegate *m_executable_field;
3245
  FileFieldDelegate *m_core_file_field;
3246
  FileFieldDelegate *m_symbol_file_field;
3247
  BooleanFieldDelegate *m_show_advanced_field;
3248
  FileFieldDelegate *m_remote_file_field;
3249
  ArchFieldDelegate *m_arch_field;
3250
  PlatformPluginFieldDelegate *m_platform_field;
3251
  ChoicesFieldDelegate *m_load_dependent_files_field;
3252
};
3253
3254
class ProcessLaunchFormDelegate : public FormDelegate {
3255
public:
3256
  ProcessLaunchFormDelegate(Debugger &debugger, WindowSP main_window_sp)
3257
0
      : m_debugger(debugger), m_main_window_sp(main_window_sp) {
3258
3259
0
    m_arguments_field = AddArgumentsField();
3260
0
    SetArgumentsFieldDefaultValue();
3261
0
    m_target_environment_field =
3262
0
        AddEnvironmentVariableListField("Target Environment Variables");
3263
0
    SetTargetEnvironmentFieldDefaultValue();
3264
0
    m_working_directory_field = AddDirectoryField(
3265
0
        "Working Directory", GetDefaultWorkingDirectory().c_str(), true, false);
3266
3267
0
    m_show_advanced_field = AddBooleanField("Show advanced settings.", false);
3268
3269
0
    m_stop_at_entry_field = AddBooleanField("Stop at entry point.", false);
3270
0
    m_detach_on_error_field =
3271
0
        AddBooleanField("Detach on error.", GetDefaultDetachOnError());
3272
0
    m_disable_aslr_field =
3273
0
        AddBooleanField("Disable ASLR", GetDefaultDisableASLR());
3274
0
    m_plugin_field = AddProcessPluginField();
3275
0
    m_arch_field = AddArchField("Architecture", "", false);
3276
0
    m_shell_field = AddFileField("Shell", "", true, false);
3277
0
    m_expand_shell_arguments_field =
3278
0
        AddBooleanField("Expand shell arguments.", false);
3279
3280
0
    m_disable_standard_io_field =
3281
0
        AddBooleanField("Disable Standard IO", GetDefaultDisableStandardIO());
3282
0
    m_standard_output_field =
3283
0
        AddFileField("Standard Output File", "", /*need_to_exist=*/false,
3284
0
                     /*required=*/false);
3285
0
    m_standard_error_field =
3286
0
        AddFileField("Standard Error File", "", /*need_to_exist=*/false,
3287
0
                     /*required=*/false);
3288
0
    m_standard_input_field =
3289
0
        AddFileField("Standard Input File", "", /*need_to_exist=*/false,
3290
0
                     /*required=*/false);
3291
3292
0
    m_show_inherited_environment_field =
3293
0
        AddBooleanField("Show inherited environment variables.", false);
3294
0
    m_inherited_environment_field =
3295
0
        AddEnvironmentVariableListField("Inherited Environment Variables");
3296
0
    SetInheritedEnvironmentFieldDefaultValue();
3297
3298
0
    AddAction("Launch", [this](Window &window) { Launch(window); });
3299
0
  }
3300
3301
0
  std::string GetName() override { return "Launch Process"; }
3302
3303
0
  void UpdateFieldsVisibility() override {
3304
0
    if (m_show_advanced_field->GetBoolean()) {
3305
0
      m_stop_at_entry_field->FieldDelegateShow();
3306
0
      m_detach_on_error_field->FieldDelegateShow();
3307
0
      m_disable_aslr_field->FieldDelegateShow();
3308
0
      m_plugin_field->FieldDelegateShow();
3309
0
      m_arch_field->FieldDelegateShow();
3310
0
      m_shell_field->FieldDelegateShow();
3311
0
      m_expand_shell_arguments_field->FieldDelegateShow();
3312
0
      m_disable_standard_io_field->FieldDelegateShow();
3313
0
      if (m_disable_standard_io_field->GetBoolean()) {
3314
0
        m_standard_input_field->FieldDelegateHide();
3315
0
        m_standard_output_field->FieldDelegateHide();
3316
0
        m_standard_error_field->FieldDelegateHide();
3317
0
      } else {
3318
0
        m_standard_input_field->FieldDelegateShow();
3319
0
        m_standard_output_field->FieldDelegateShow();
3320
0
        m_standard_error_field->FieldDelegateShow();
3321
0
      }
3322
0
      m_show_inherited_environment_field->FieldDelegateShow();
3323
0
      if (m_show_inherited_environment_field->GetBoolean())
3324
0
        m_inherited_environment_field->FieldDelegateShow();
3325
0
      else
3326
0
        m_inherited_environment_field->FieldDelegateHide();
3327
0
    } else {
3328
0
      m_stop_at_entry_field->FieldDelegateHide();
3329
0
      m_detach_on_error_field->FieldDelegateHide();
3330
0
      m_disable_aslr_field->FieldDelegateHide();
3331
0
      m_plugin_field->FieldDelegateHide();
3332
0
      m_arch_field->FieldDelegateHide();
3333
0
      m_shell_field->FieldDelegateHide();
3334
0
      m_expand_shell_arguments_field->FieldDelegateHide();
3335
0
      m_disable_standard_io_field->FieldDelegateHide();
3336
0
      m_standard_input_field->FieldDelegateHide();
3337
0
      m_standard_output_field->FieldDelegateHide();
3338
0
      m_standard_error_field->FieldDelegateHide();
3339
0
      m_show_inherited_environment_field->FieldDelegateHide();
3340
0
      m_inherited_environment_field->FieldDelegateHide();
3341
0
    }
3342
0
  }
3343
3344
  // Methods for setting the default value of the fields.
3345
3346
0
  void SetArgumentsFieldDefaultValue() {
3347
0
    TargetSP target = m_debugger.GetSelectedTarget();
3348
0
    if (target == nullptr)
3349
0
      return;
3350
3351
0
    const Args &target_arguments =
3352
0
        target->GetProcessLaunchInfo().GetArguments();
3353
0
    m_arguments_field->AddArguments(target_arguments);
3354
0
  }
3355
3356
0
  void SetTargetEnvironmentFieldDefaultValue() {
3357
0
    TargetSP target = m_debugger.GetSelectedTarget();
3358
0
    if (target == nullptr)
3359
0
      return;
3360
3361
0
    const Environment &target_environment = target->GetTargetEnvironment();
3362
0
    m_target_environment_field->AddEnvironmentVariables(target_environment);
3363
0
  }
3364
3365
0
  void SetInheritedEnvironmentFieldDefaultValue() {
3366
0
    TargetSP target = m_debugger.GetSelectedTarget();
3367
0
    if (target == nullptr)
3368
0
      return;
3369
3370
0
    const Environment &inherited_environment =
3371
0
        target->GetInheritedEnvironment();
3372
0
    m_inherited_environment_field->AddEnvironmentVariables(
3373
0
        inherited_environment);
3374
0
  }
3375
3376
0
  std::string GetDefaultWorkingDirectory() {
3377
0
    TargetSP target = m_debugger.GetSelectedTarget();
3378
0
    if (target == nullptr)
3379
0
      return "";
3380
3381
0
    PlatformSP platform = target->GetPlatform();
3382
0
    return platform->GetWorkingDirectory().GetPath();
3383
0
  }
3384
3385
0
  bool GetDefaultDisableASLR() {
3386
0
    TargetSP target = m_debugger.GetSelectedTarget();
3387
0
    if (target == nullptr)
3388
0
      return false;
3389
3390
0
    return target->GetDisableASLR();
3391
0
  }
3392
3393
0
  bool GetDefaultDisableStandardIO() {
3394
0
    TargetSP target = m_debugger.GetSelectedTarget();
3395
0
    if (target == nullptr)
3396
0
      return true;
3397
3398
0
    return target->GetDisableSTDIO();
3399
0
  }
3400
3401
0
  bool GetDefaultDetachOnError() {
3402
0
    TargetSP target = m_debugger.GetSelectedTarget();
3403
0
    if (target == nullptr)
3404
0
      return true;
3405
3406
0
    return target->GetDetachOnError();
3407
0
  }
3408
3409
  // Methods for getting the necessary information and setting them to the
3410
  // ProcessLaunchInfo.
3411
3412
0
  void GetExecutableSettings(ProcessLaunchInfo &launch_info) {
3413
0
    TargetSP target = m_debugger.GetSelectedTarget();
3414
0
    ModuleSP executable_module = target->GetExecutableModule();
3415
0
    llvm::StringRef target_settings_argv0 = target->GetArg0();
3416
3417
0
    if (!target_settings_argv0.empty()) {
3418
0
      launch_info.GetArguments().AppendArgument(target_settings_argv0);
3419
0
      launch_info.SetExecutableFile(executable_module->GetPlatformFileSpec(),
3420
0
                                    false);
3421
0
      return;
3422
0
    }
3423
3424
0
    launch_info.SetExecutableFile(executable_module->GetPlatformFileSpec(),
3425
0
                                  true);
3426
0
  }
3427
3428
0
  void GetArguments(ProcessLaunchInfo &launch_info) {
3429
0
    TargetSP target = m_debugger.GetSelectedTarget();
3430
0
    Args arguments = m_arguments_field->GetArguments();
3431
0
    launch_info.GetArguments().AppendArguments(arguments);
3432
0
  }
3433
3434
0
  void GetEnvironment(ProcessLaunchInfo &launch_info) {
3435
0
    Environment target_environment =
3436
0
        m_target_environment_field->GetEnvironment();
3437
0
    Environment inherited_environment =
3438
0
        m_inherited_environment_field->GetEnvironment();
3439
0
    launch_info.GetEnvironment().insert(target_environment.begin(),
3440
0
                                        target_environment.end());
3441
0
    launch_info.GetEnvironment().insert(inherited_environment.begin(),
3442
0
                                        inherited_environment.end());
3443
0
  }
3444
3445
0
  void GetWorkingDirectory(ProcessLaunchInfo &launch_info) {
3446
0
    if (m_working_directory_field->IsSpecified())
3447
0
      launch_info.SetWorkingDirectory(
3448
0
          m_working_directory_field->GetResolvedFileSpec());
3449
0
  }
3450
3451
0
  void GetStopAtEntry(ProcessLaunchInfo &launch_info) {
3452
0
    if (m_stop_at_entry_field->GetBoolean())
3453
0
      launch_info.GetFlags().Set(eLaunchFlagStopAtEntry);
3454
0
    else
3455
0
      launch_info.GetFlags().Clear(eLaunchFlagStopAtEntry);
3456
0
  }
3457
3458
0
  void GetDetachOnError(ProcessLaunchInfo &launch_info) {
3459
0
    if (m_detach_on_error_field->GetBoolean())
3460
0
      launch_info.GetFlags().Set(eLaunchFlagDetachOnError);
3461
0
    else
3462
0
      launch_info.GetFlags().Clear(eLaunchFlagDetachOnError);
3463
0
  }
3464
3465
0
  void GetDisableASLR(ProcessLaunchInfo &launch_info) {
3466
0
    if (m_disable_aslr_field->GetBoolean())
3467
0
      launch_info.GetFlags().Set(eLaunchFlagDisableASLR);
3468
0
    else
3469
0
      launch_info.GetFlags().Clear(eLaunchFlagDisableASLR);
3470
0
  }
3471
3472
0
  void GetPlugin(ProcessLaunchInfo &launch_info) {
3473
0
    launch_info.SetProcessPluginName(m_plugin_field->GetPluginName());
3474
0
  }
3475
3476
0
  void GetArch(ProcessLaunchInfo &launch_info) {
3477
0
    if (!m_arch_field->IsSpecified())
3478
0
      return;
3479
3480
0
    TargetSP target_sp = m_debugger.GetSelectedTarget();
3481
0
    PlatformSP platform_sp =
3482
0
        target_sp ? target_sp->GetPlatform() : PlatformSP();
3483
0
    launch_info.GetArchitecture() = Platform::GetAugmentedArchSpec(
3484
0
        platform_sp.get(), m_arch_field->GetArchString());
3485
0
  }
3486
3487
0
  void GetShell(ProcessLaunchInfo &launch_info) {
3488
0
    if (!m_shell_field->IsSpecified())
3489
0
      return;
3490
3491
0
    launch_info.SetShell(m_shell_field->GetResolvedFileSpec());
3492
0
    launch_info.SetShellExpandArguments(
3493
0
        m_expand_shell_arguments_field->GetBoolean());
3494
0
  }
3495
3496
0
  void GetStandardIO(ProcessLaunchInfo &launch_info) {
3497
0
    if (m_disable_standard_io_field->GetBoolean()) {
3498
0
      launch_info.GetFlags().Set(eLaunchFlagDisableSTDIO);
3499
0
      return;
3500
0
    }
3501
3502
0
    FileAction action;
3503
0
    if (m_standard_input_field->IsSpecified()) {
3504
0
      action.Open(STDIN_FILENO, m_standard_input_field->GetFileSpec(), true,
3505
0
                  false);
3506
0
      launch_info.AppendFileAction(action);
3507
0
    }
3508
0
    if (m_standard_output_field->IsSpecified()) {
3509
0
      action.Open(STDOUT_FILENO, m_standard_output_field->GetFileSpec(), false,
3510
0
                  true);
3511
0
      launch_info.AppendFileAction(action);
3512
0
    }
3513
0
    if (m_standard_error_field->IsSpecified()) {
3514
0
      action.Open(STDERR_FILENO, m_standard_error_field->GetFileSpec(), false,
3515
0
                  true);
3516
0
      launch_info.AppendFileAction(action);
3517
0
    }
3518
0
  }
3519
3520
0
  void GetInheritTCC(ProcessLaunchInfo &launch_info) {
3521
0
    if (m_debugger.GetSelectedTarget()->GetInheritTCC())
3522
0
      launch_info.GetFlags().Set(eLaunchFlagInheritTCCFromParent);
3523
0
  }
3524
3525
0
  ProcessLaunchInfo GetLaunchInfo() {
3526
0
    ProcessLaunchInfo launch_info;
3527
3528
0
    GetExecutableSettings(launch_info);
3529
0
    GetArguments(launch_info);
3530
0
    GetEnvironment(launch_info);
3531
0
    GetWorkingDirectory(launch_info);
3532
0
    GetStopAtEntry(launch_info);
3533
0
    GetDetachOnError(launch_info);
3534
0
    GetDisableASLR(launch_info);
3535
0
    GetPlugin(launch_info);
3536
0
    GetArch(launch_info);
3537
0
    GetShell(launch_info);
3538
0
    GetStandardIO(launch_info);
3539
0
    GetInheritTCC(launch_info);
3540
3541
0
    return launch_info;
3542
0
  }
3543
3544
0
  bool StopRunningProcess() {
3545
0
    ExecutionContext exe_ctx =
3546
0
        m_debugger.GetCommandInterpreter().GetExecutionContext();
3547
3548
0
    if (!exe_ctx.HasProcessScope())
3549
0
      return false;
3550
3551
0
    Process *process = exe_ctx.GetProcessPtr();
3552
0
    if (!(process && process->IsAlive()))
3553
0
      return false;
3554
3555
0
    FormDelegateSP form_delegate_sp =
3556
0
        FormDelegateSP(new DetachOrKillProcessFormDelegate(process));
3557
0
    Rect bounds = m_main_window_sp->GetCenteredRect(85, 8);
3558
0
    WindowSP form_window_sp = m_main_window_sp->CreateSubWindow(
3559
0
        form_delegate_sp->GetName().c_str(), bounds, true);
3560
0
    WindowDelegateSP window_delegate_sp =
3561
0
        WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));
3562
0
    form_window_sp->SetDelegate(window_delegate_sp);
3563
3564
0
    return true;
3565
0
  }
3566
3567
0
  Target *GetTarget() {
3568
0
    Target *target = m_debugger.GetSelectedTarget().get();
3569
3570
0
    if (target == nullptr) {
3571
0
      SetError("No target exists!");
3572
0
      return nullptr;
3573
0
    }
3574
3575
0
    ModuleSP exe_module_sp = target->GetExecutableModule();
3576
3577
0
    if (exe_module_sp == nullptr) {
3578
0
      SetError("No executable in target!");
3579
0
      return nullptr;
3580
0
    }
3581
3582
0
    return target;
3583
0
  }
3584
3585
0
  void Launch(Window &window) {
3586
0
    ClearError();
3587
3588
0
    bool all_fields_are_valid = CheckFieldsValidity();
3589
0
    if (!all_fields_are_valid)
3590
0
      return;
3591
3592
0
    bool process_is_running = StopRunningProcess();
3593
0
    if (process_is_running)
3594
0
      return;
3595
3596
0
    Target *target = GetTarget();
3597
0
    if (HasError())
3598
0
      return;
3599
3600
0
    StreamString stream;
3601
0
    ProcessLaunchInfo launch_info = GetLaunchInfo();
3602
0
    Status status = target->Launch(launch_info, &stream);
3603
3604
0
    if (status.Fail()) {
3605
0
      SetError(status.AsCString());
3606
0
      return;
3607
0
    }
3608
3609
0
    ProcessSP process_sp(target->GetProcessSP());
3610
0
    if (!process_sp) {
3611
0
      SetError("Launched successfully but target has no process!");
3612
0
      return;
3613
0
    }
3614
3615
0
    window.GetParent()->RemoveSubWindow(&window);
3616
0
  }
3617
3618
protected:
3619
  Debugger &m_debugger;
3620
  WindowSP m_main_window_sp;
3621
3622
  ArgumentsFieldDelegate *m_arguments_field;
3623
  EnvironmentVariableListFieldDelegate *m_target_environment_field;
3624
  DirectoryFieldDelegate *m_working_directory_field;
3625
3626
  BooleanFieldDelegate *m_show_advanced_field;
3627
3628
  BooleanFieldDelegate *m_stop_at_entry_field;
3629
  BooleanFieldDelegate *m_detach_on_error_field;
3630
  BooleanFieldDelegate *m_disable_aslr_field;
3631
  ProcessPluginFieldDelegate *m_plugin_field;
3632
  ArchFieldDelegate *m_arch_field;
3633
  FileFieldDelegate *m_shell_field;
3634
  BooleanFieldDelegate *m_expand_shell_arguments_field;
3635
  BooleanFieldDelegate *m_disable_standard_io_field;
3636
  FileFieldDelegate *m_standard_input_field;
3637
  FileFieldDelegate *m_standard_output_field;
3638
  FileFieldDelegate *m_standard_error_field;
3639
3640
  BooleanFieldDelegate *m_show_inherited_environment_field;
3641
  EnvironmentVariableListFieldDelegate *m_inherited_environment_field;
3642
};
3643
3644
////////////
3645
// Searchers
3646
////////////
3647
3648
class SearcherDelegate {
3649
public:
3650
0
  SearcherDelegate() {}
3651
3652
  virtual ~SearcherDelegate() = default;
3653
3654
  virtual int GetNumberOfMatches() = 0;
3655
3656
  // Get the string that will be displayed for the match at the input index.
3657
  virtual const std::string &GetMatchTextAtIndex(int index) = 0;
3658
3659
  // Update the matches of the search. This is executed every time the text
3660
  // field handles an event.
3661
  virtual void UpdateMatches(const std::string &text) = 0;
3662
3663
  // Execute the user callback given the index of some match. This is executed
3664
  // once the user selects a match.
3665
  virtual void ExecuteCallback(int match_index) = 0;
3666
};
3667
3668
typedef std::shared_ptr<SearcherDelegate> SearcherDelegateSP;
3669
3670
class SearcherWindowDelegate : public WindowDelegate {
3671
public:
3672
  SearcherWindowDelegate(SearcherDelegateSP &delegate_sp)
3673
      : m_delegate_sp(delegate_sp), m_text_field("Search", "", false),
3674
0
        m_selected_match(0), m_first_visible_match(0) {
3675
0
    ;
3676
0
  }
3677
3678
  // A completion window is padded by one character from all sides. A text field
3679
  // is first drawn for inputting the searcher request, then a list of matches
3680
  // are displayed in a scrollable list.
3681
  //
3682
  // ___<Searcher Window Name>____________________________
3683
  // |                                                   |
3684
  // | __[Search]_______________________________________ |
3685
  // | |                                               | |
3686
  // | |_______________________________________________| |
3687
  // | - Match 1.                                        |
3688
  // | - Match 2.                                        |
3689
  // | - ...                                             |
3690
  // |                                                   |
3691
  // |____________________________[Press Esc to Cancel]__|
3692
  //
3693
3694
  // Get the index of the last visible match. Assuming at least one match
3695
  // exists.
3696
0
  int GetLastVisibleMatch(int height) {
3697
0
    int index = m_first_visible_match + height;
3698
0
    return std::min(index, m_delegate_sp->GetNumberOfMatches()) - 1;
3699
0
  }
3700
3701
0
  int GetNumberOfVisibleMatches(int height) {
3702
0
    return GetLastVisibleMatch(height) - m_first_visible_match + 1;
3703
0
  }
3704
3705
0
  void UpdateScrolling(Surface &surface) {
3706
0
    if (m_selected_match < m_first_visible_match) {
3707
0
      m_first_visible_match = m_selected_match;
3708
0
      return;
3709
0
    }
3710
0
3711
0
    int height = surface.GetHeight();
3712
0
    int last_visible_match = GetLastVisibleMatch(height);
3713
0
    if (m_selected_match > last_visible_match) {
3714
0
      m_first_visible_match = m_selected_match - height + 1;
3715
0
    }
3716
0
  }
3717
3718
0
  void DrawMatches(Surface &surface) {
3719
0
    if (m_delegate_sp->GetNumberOfMatches() == 0)
3720
0
      return;
3721
0
3722
0
    UpdateScrolling(surface);
3723
0
3724
0
    int count = GetNumberOfVisibleMatches(surface.GetHeight());
3725
0
    for (int i = 0; i < count; i++) {
3726
0
      surface.MoveCursor(1, i);
3727
0
      int current_match = m_first_visible_match + i;
3728
0
      if (current_match == m_selected_match)
3729
0
        surface.AttributeOn(A_REVERSE);
3730
0
      surface.PutCString(
3731
0
          m_delegate_sp->GetMatchTextAtIndex(current_match).c_str());
3732
0
      if (current_match == m_selected_match)
3733
0
        surface.AttributeOff(A_REVERSE);
3734
0
    }
3735
0
  }
3736
3737
0
  void DrawContent(Surface &surface) {
3738
0
    Rect content_bounds = surface.GetFrame();
3739
0
    Rect text_field_bounds, matchs_bounds;
3740
0
    content_bounds.HorizontalSplit(m_text_field.FieldDelegateGetHeight(),
3741
0
                                   text_field_bounds, matchs_bounds);
3742
0
    Surface text_field_surface = surface.SubSurface(text_field_bounds);
3743
0
    Surface matches_surface = surface.SubSurface(matchs_bounds);
3744
0
3745
0
    m_text_field.FieldDelegateDraw(text_field_surface, true);
3746
0
    DrawMatches(matches_surface);
3747
0
  }
3748
3749
0
  bool WindowDelegateDraw(Window &window, bool force) override {
3750
0
    window.Erase();
3751
0
3752
0
    window.DrawTitleBox(window.GetName(), "Press Esc to Cancel");
3753
0
3754
0
    Rect content_bounds = window.GetFrame();
3755
0
    content_bounds.Inset(2, 2);
3756
0
    Surface content_surface = window.SubSurface(content_bounds);
3757
0
3758
0
    DrawContent(content_surface);
3759
0
    return true;
3760
0
  }
3761
3762
0
  void SelectNext() {
3763
0
    if (m_selected_match != m_delegate_sp->GetNumberOfMatches() - 1)
3764
0
      m_selected_match++;
3765
0
  }
3766
3767
0
  void SelectPrevious() {
3768
0
    if (m_selected_match != 0)
3769
0
      m_selected_match--;
3770
0
  }
3771
3772
0
  void ExecuteCallback(Window &window) {
3773
0
    m_delegate_sp->ExecuteCallback(m_selected_match);
3774
0
    window.GetParent()->RemoveSubWindow(&window);
3775
0
  }
3776
3777
0
  void UpdateMatches() {
3778
0
    m_delegate_sp->UpdateMatches(m_text_field.GetText());
3779
0
    m_selected_match = 0;
3780
0
  }
3781
3782
0
  HandleCharResult WindowDelegateHandleChar(Window &window, int key) override {
3783
0
    switch (key) {
3784
0
    case '\r':
3785
0
    case '\n':
3786
0
    case KEY_ENTER:
3787
0
      ExecuteCallback(window);
3788
0
      return eKeyHandled;
3789
0
    case '\t':
3790
0
    case KEY_DOWN:
3791
0
      SelectNext();
3792
0
      return eKeyHandled;
3793
0
    case KEY_SHIFT_TAB:
3794
0
    case KEY_UP:
3795
0
      SelectPrevious();
3796
0
      return eKeyHandled;
3797
0
    case KEY_ESCAPE:
3798
0
      window.GetParent()->RemoveSubWindow(&window);
3799
0
      return eKeyHandled;
3800
0
    default:
3801
0
      break;
3802
0
    }
3803
0
3804
0
    if (m_text_field.FieldDelegateHandleChar(key) == eKeyHandled)
3805
0
      UpdateMatches();
3806
0
3807
0
    return eKeyHandled;
3808
0
  }
3809
3810
protected:
3811
  SearcherDelegateSP m_delegate_sp;
3812
  TextFieldDelegate m_text_field;
3813
  // The index of the currently selected match.
3814
  int m_selected_match;
3815
  // The index of the first visible match.
3816
  int m_first_visible_match;
3817
};
3818
3819
//////////////////////////////
3820
// Searcher Delegate Instances
3821
//////////////////////////////
3822
3823
// This is a searcher delegate wrapper around CommandCompletions common
3824
// callbacks. The callbacks are only given the match string. The completion_mask
3825
// can be a combination of CommonCompletionTypes.
3826
class CommonCompletionSearcherDelegate : public SearcherDelegate {
3827
public:
3828
  typedef std::function<void(const std::string &)> CallbackType;
3829
3830
  CommonCompletionSearcherDelegate(Debugger &debugger, uint32_t completion_mask,
3831
                                   CallbackType callback)
3832
      : m_debugger(debugger), m_completion_mask(completion_mask),
3833
0
        m_callback(callback) {}
3834
3835
0
  int GetNumberOfMatches() override { return m_matches.GetSize(); }
3836
3837
0
  const std::string &GetMatchTextAtIndex(int index) override {
3838
0
    return m_matches[index];
3839
0
  }
3840
3841
0
  void UpdateMatches(const std::string &text) override {
3842
0
    CompletionResult result;
3843
0
    CompletionRequest request(text.c_str(), text.size(), result);
3844
0
    CommandCompletions::InvokeCommonCompletionCallbacks(
3845
0
        m_debugger.GetCommandInterpreter(), m_completion_mask, request,
3846
0
        nullptr);
3847
0
    result.GetMatches(m_matches);
3848
0
  }
3849
3850
0
  void ExecuteCallback(int match_index) override {
3851
0
    m_callback(m_matches[match_index]);
3852
0
  }
3853
3854
protected:
3855
  Debugger &m_debugger;
3856
  // A compound mask from CommonCompletionTypes.
3857
  uint32_t m_completion_mask;
3858
  // A callback to execute once the user selects a match. The match is passed to
3859
  // the callback as a string.
3860
  CallbackType m_callback;
3861
  StringList m_matches;
3862
};
3863
3864
////////
3865
// Menus
3866
////////
3867
3868
class MenuDelegate {
3869
public:
3870
0
  virtual ~MenuDelegate() = default;
3871
3872
  virtual MenuActionResult MenuDelegateAction(Menu &menu) = 0;
3873
};
3874
3875
class Menu : public WindowDelegate {
3876
public:
3877
  enum class Type { Invalid, Bar, Item, Separator };
3878
3879
  // Menubar or separator constructor
3880
  Menu(Type type);
3881
3882
  // Menuitem constructor
3883
  Menu(const char *name, const char *key_name, int key_value,
3884
       uint64_t identifier);
3885
3886
0
  ~Menu() override = default;
3887
3888
0
  const MenuDelegateSP &GetDelegate() const { return m_delegate_sp; }
3889
3890
0
  void SetDelegate(const MenuDelegateSP &delegate_sp) {
3891
0
    m_delegate_sp = delegate_sp;
3892
0
  }
3893
3894
  void RecalculateNameLengths();
3895
3896
  void AddSubmenu(const MenuSP &menu_sp);
3897
3898
  int DrawAndRunMenu(Window &window);
3899
3900
  void DrawMenuTitle(Window &window, bool highlight);
3901
3902
  bool WindowDelegateDraw(Window &window, bool force) override;
3903
3904
  HandleCharResult WindowDelegateHandleChar(Window &window, int key) override;
3905
3906
0
  MenuActionResult ActionPrivate(Menu &menu) {
3907
0
    MenuActionResult result = MenuActionResult::NotHandled;
3908
0
    if (m_delegate_sp) {
3909
0
      result = m_delegate_sp->MenuDelegateAction(menu);
3910
0
      if (result != MenuActionResult::NotHandled)
3911
0
        return result;
3912
0
    } else if (m_parent) {
3913
0
      result = m_parent->ActionPrivate(menu);
3914
0
      if (result != MenuActionResult::NotHandled)
3915
0
        return result;
3916
0
    }
3917
0
    return m_canned_result;
3918
0
  }
3919
3920
0
  MenuActionResult Action() {
3921
    // Call the recursive action so it can try to handle it with the menu
3922
    // delegate, and if not, try our parent menu
3923
0
    return ActionPrivate(*this);
3924
0
  }
3925
3926
0
  void SetCannedResult(MenuActionResult result) { m_canned_result = result; }
3927
3928
0
  Menus &GetSubmenus() { return m_submenus; }
3929
3930
0
  const Menus &GetSubmenus() const { return m_submenus; }
3931
3932
0
  int GetSelectedSubmenuIndex() const { return m_selected; }
3933
3934
0
  void SetSelectedSubmenuIndex(int idx) { m_selected = idx; }
3935
3936
0
  Type GetType() const { return m_type; }
3937
3938
0
  int GetStartingColumn() const { return m_start_col; }
3939
3940
0
  void SetStartingColumn(int col) { m_start_col = col; }
3941
3942
0
  int GetKeyValue() const { return m_key_value; }
3943
3944
0
  std::string &GetName() { return m_name; }
3945
3946
0
  int GetDrawWidth() const {
3947
0
    return m_max_submenu_name_length + m_max_submenu_key_name_length + 8;
3948
0
  }
3949
3950
0
  uint64_t GetIdentifier() const { return m_identifier; }
3951
3952
0
  void SetIdentifier(uint64_t identifier) { m_identifier = identifier; }
3953
3954
protected:
3955
  std::string m_name;
3956
  std::string m_key_name;
3957
  uint64_t m_identifier;
3958
  Type m_type;
3959
  int m_key_value;
3960
  int m_start_col;
3961
  int m_max_submenu_name_length;
3962
  int m_max_submenu_key_name_length;
3963
  int m_selected;
3964
  Menu *m_parent;
3965
  Menus m_submenus;
3966
  WindowSP m_menu_window_sp;
3967
  MenuActionResult m_canned_result;
3968
  MenuDelegateSP m_delegate_sp;
3969
};
3970
3971
// Menubar or separator constructor
3972
Menu::Menu(Type type)
3973
    : m_name(), m_key_name(), m_identifier(0), m_type(type), m_key_value(0),
3974
      m_start_col(0), m_max_submenu_name_length(0),
3975
      m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr),
3976
      m_submenus(), m_canned_result(MenuActionResult::NotHandled),
3977
0
      m_delegate_sp() {}
3978
3979
// Menuitem constructor
3980
Menu::Menu(const char *name, const char *key_name, int key_value,
3981
           uint64_t identifier)
3982
    : m_name(), m_key_name(), m_identifier(identifier), m_type(Type::Invalid),
3983
      m_key_value(key_value), m_start_col(0), m_max_submenu_name_length(0),
3984
      m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr),
3985
      m_submenus(), m_canned_result(MenuActionResult::NotHandled),
3986
0
      m_delegate_sp() {
3987
0
  if (name && name[0]) {
3988
0
    m_name = name;
3989
0
    m_type = Type::Item;
3990
0
    if (key_name && key_name[0])
3991
0
      m_key_name = key_name;
3992
0
  } else {
3993
0
    m_type = Type::Separator;
3994
0
  }
3995
0
}
3996
3997
0
void Menu::RecalculateNameLengths() {
3998
0
  m_max_submenu_name_length = 0;
3999
0
  m_max_submenu_key_name_length = 0;
4000
0
  Menus &submenus = GetSubmenus();
4001
0
  const size_t num_submenus = submenus.size();
4002
0
  for (size_t i = 0; i < num_submenus; ++i) {
4003
0
    Menu *submenu = submenus[i].get();
4004
0
    if (static_cast<size_t>(m_max_submenu_name_length) < submenu->m_name.size())
4005
0
      m_max_submenu_name_length = submenu->m_name.size();
4006
0
    if (static_cast<size_t>(m_max_submenu_key_name_length) <
4007
0
        submenu->m_key_name.size())
4008
0
      m_max_submenu_key_name_length = submenu->m_key_name.size();
4009
0
  }
4010
0
}
4011
4012
0
void Menu::AddSubmenu(const MenuSP &menu_sp) {
4013
0
  menu_sp->m_parent = this;
4014
0
  if (static_cast<size_t>(m_max_submenu_name_length) < menu_sp->m_name.size())
4015
0
    m_max_submenu_name_length = menu_sp->m_name.size();
4016
0
  if (static_cast<size_t>(m_max_submenu_key_name_length) <
4017
0
      menu_sp->m_key_name.size())
4018
0
    m_max_submenu_key_name_length = menu_sp->m_key_name.size();
4019
0
  m_submenus.push_back(menu_sp);
4020
0
}
4021
4022
0
void Menu::DrawMenuTitle(Window &window, bool highlight) {
4023
0
  if (m_type == Type::Separator) {
4024
0
    window.MoveCursor(0, window.GetCursorY());
4025
0
    window.PutChar(ACS_LTEE);
4026
0
    int width = window.GetWidth();
4027
0
    if (width > 2) {
4028
0
      width -= 2;
4029
0
      for (int i = 0; i < width; ++i)
4030
0
        window.PutChar(ACS_HLINE);
4031
0
    }
4032
0
    window.PutChar(ACS_RTEE);
4033
0
  } else {
4034
0
    const int shortcut_key = m_key_value;
4035
0
    bool underlined_shortcut = false;
4036
0
    const attr_t highlight_attr = A_REVERSE;
4037
0
    if (highlight)
4038
0
      window.AttributeOn(highlight_attr);
4039
0
    if (llvm::isPrint(shortcut_key)) {
4040
0
      size_t lower_pos = m_name.find(tolower(shortcut_key));
4041
0
      size_t upper_pos = m_name.find(toupper(shortcut_key));
4042
0
      const char *name = m_name.c_str();
4043
0
      size_t pos = std::min<size_t>(lower_pos, upper_pos);
4044
0
      if (pos != std::string::npos) {
4045
0
        underlined_shortcut = true;
4046
0
        if (pos > 0) {
4047
0
          window.PutCString(name, pos);
4048
0
          name += pos;
4049
0
        }
4050
0
        const attr_t shortcut_attr = A_UNDERLINE | A_BOLD;
4051
0
        window.AttributeOn(shortcut_attr);
4052
0
        window.PutChar(name[0]);
4053
0
        window.AttributeOff(shortcut_attr);
4054
0
        name++;
4055
0
        if (name[0])
4056
0
          window.PutCString(name);
4057
0
      }
4058
0
    }
4059
4060
0
    if (!underlined_shortcut) {
4061
0
      window.PutCString(m_name.c_str());
4062
0
    }
4063
4064
0
    if (highlight)
4065
0
      window.AttributeOff(highlight_attr);
4066
4067
0
    if (m_key_name.empty()) {
4068
0
      if (!underlined_shortcut && llvm::isPrint(m_key_value)) {
4069
0
        window.AttributeOn(COLOR_PAIR(MagentaOnWhite));
4070
0
        window.Printf(" (%c)", m_key_value);
4071
0
        window.AttributeOff(COLOR_PAIR(MagentaOnWhite));
4072
0
      }
4073
0
    } else {
4074
0
      window.AttributeOn(COLOR_PAIR(MagentaOnWhite));
4075
0
      window.Printf(" (%s)", m_key_name.c_str());
4076
0
      window.AttributeOff(COLOR_PAIR(MagentaOnWhite));
4077
0
    }
4078
0
  }
4079
0
}
4080
4081
0
bool Menu::WindowDelegateDraw(Window &window, bool force) {
4082
0
  Menus &submenus = GetSubmenus();
4083
0
  const size_t num_submenus = submenus.size();
4084
0
  const int selected_idx = GetSelectedSubmenuIndex();
4085
0
  Menu::Type menu_type = GetType();
4086
0
  switch (menu_type) {
4087
0
  case Menu::Type::Bar: {
4088
0
    window.SetBackground(BlackOnWhite);
4089
0
    window.MoveCursor(0, 0);
4090
0
    for (size_t i = 0; i < num_submenus; ++i) {
4091
0
      Menu *menu = submenus[i].get();
4092
0
      if (i > 0)
4093
0
        window.PutChar(' ');
4094
0
      menu->SetStartingColumn(window.GetCursorX());
4095
0
      window.PutCString("| ");
4096
0
      menu->DrawMenuTitle(window, false);
4097
0
    }
4098
0
    window.PutCString(" |");
4099
0
  } break;
4100
4101
0
  case Menu::Type::Item: {
4102
0
    int y = 1;
4103
0
    int x = 3;
4104
    // Draw the menu
4105
0
    int cursor_x = 0;
4106
0
    int cursor_y = 0;
4107
0
    window.Erase();
4108
0
    window.SetBackground(BlackOnWhite);
4109
0
    window.Box();
4110
0
    for (size_t i = 0; i < num_submenus; ++i) {
4111
0
      const bool is_selected = (i == static_cast<size_t>(selected_idx));
4112
0
      window.MoveCursor(x, y + i);
4113
0
      if (is_selected) {
4114
        // Remember where we want the cursor to be
4115
0
        cursor_x = x - 1;
4116
0
        cursor_y = y + i;
4117
0
      }
4118
0
      submenus[i]->DrawMenuTitle(window, is_selected);
4119
0
    }
4120
0
    window.MoveCursor(cursor_x, cursor_y);
4121
0
  } break;
4122
4123
0
  default:
4124
0
  case Menu::Type::Separator:
4125
0
    break;
4126
0
  }
4127
0
  return true; // Drawing handled...
4128
0
}
4129
4130
0
HandleCharResult Menu::WindowDelegateHandleChar(Window &window, int key) {
4131
0
  HandleCharResult result = eKeyNotHandled;
4132
4133
0
  Menus &submenus = GetSubmenus();
4134
0
  const size_t num_submenus = submenus.size();
4135
0
  const int selected_idx = GetSelectedSubmenuIndex();
4136
0
  Menu::Type menu_type = GetType();
4137
0
  if (menu_type == Menu::Type::Bar) {
4138
0
    MenuSP run_menu_sp;
4139
0
    switch (key) {
4140
0
    case KEY_DOWN:
4141
0
    case KEY_UP:
4142
      // Show last menu or first menu
4143
0
      if (selected_idx < static_cast<int>(num_submenus))
4144
0
        run_menu_sp = submenus[selected_idx];
4145
0
      else if (!submenus.empty())
4146
0
        run_menu_sp = submenus.front();
4147
0
      result = eKeyHandled;
4148
0
      break;
4149
4150
0
    case KEY_RIGHT:
4151
0
      ++m_selected;
4152
0
      if (m_selected >= static_cast<int>(num_submenus))
4153
0
        m_selected = 0;
4154
0
      if (m_selected < static_cast<int>(num_submenus))
4155
0
        run_menu_sp = submenus[m_selected];
4156
0
      else if (!submenus.empty())
4157
0
        run_menu_sp = submenus.front();
4158
0
      result = eKeyHandled;
4159
0
      break;
4160
4161
0
    case KEY_LEFT:
4162
0
      --m_selected;
4163
0
      if (m_selected < 0)
4164
0
        m_selected = num_submenus - 1;
4165
0
      if (m_selected < static_cast<int>(num_submenus))
4166
0
        run_menu_sp = submenus[m_selected];
4167
0
      else if (!submenus.empty())
4168
0
        run_menu_sp = submenus.front();
4169
0
      result = eKeyHandled;
4170
0
      break;
4171
4172
0
    default:
4173
0
      for (size_t i = 0; i < num_submenus; ++i) {
4174
0
        if (submenus[i]->GetKeyValue() == key) {
4175
0
          SetSelectedSubmenuIndex(i);
4176
0
          run_menu_sp = submenus[i];
4177
0
          result = eKeyHandled;
4178
0
          break;
4179
0
        }
4180
0
      }
4181
0
      break;
4182
0
    }
4183
4184
0
    if (run_menu_sp) {
4185
      // Run the action on this menu in case we need to populate the menu with
4186
      // dynamic content and also in case check marks, and any other menu
4187
      // decorations need to be calculated
4188
0
      if (run_menu_sp->Action() == MenuActionResult::Quit)
4189
0
        return eQuitApplication;
4190
4191
0
      Rect menu_bounds;
4192
0
      menu_bounds.origin.x = run_menu_sp->GetStartingColumn();
4193
0
      menu_bounds.origin.y = 1;
4194
0
      menu_bounds.size.width = run_menu_sp->GetDrawWidth();
4195
0
      menu_bounds.size.height = run_menu_sp->GetSubmenus().size() + 2;
4196
0
      if (m_menu_window_sp)
4197
0
        window.GetParent()->RemoveSubWindow(m_menu_window_sp.get());
4198
4199
0
      m_menu_window_sp = window.GetParent()->CreateSubWindow(
4200
0
          run_menu_sp->GetName().c_str(), menu_bounds, true);
4201
0
      m_menu_window_sp->SetDelegate(run_menu_sp);
4202
0
    }
4203
0
  } else if (menu_type == Menu::Type::Item) {
4204
0
    switch (key) {
4205
0
    case KEY_DOWN:
4206
0
      if (m_submenus.size() > 1) {
4207
0
        const int start_select = m_selected;
4208
0
        while (++m_selected != start_select) {
4209
0
          if (static_cast<size_t>(m_selected) >= num_submenus)
4210
0
            m_selected = 0;
4211
0
          if (m_submenus[m_selected]->GetType() == Type::Separator)
4212
0
            continue;
4213
0
          else
4214
0
            break;
4215
0
        }
4216
0
        return eKeyHandled;
4217
0
      }
4218
0
      break;
4219
4220
0
    case KEY_UP:
4221
0
      if (m_submenus.size() > 1) {
4222
0
        const int start_select = m_selected;
4223
0
        while (--m_selected != start_select) {
4224
0
          if (m_selected < static_cast<int>(0))
4225
0
            m_selected = num_submenus - 1;
4226
0
          if (m_submenus[m_selected]->GetType() == Type::Separator)
4227
0
            continue;
4228
0
          else
4229
0
            break;
4230
0
        }
4231
0
        return eKeyHandled;
4232
0
      }
4233
0
      break;
4234
4235
0
    case KEY_RETURN:
4236
0
      if (static_cast<size_t>(selected_idx) < num_submenus) {
4237
0
        if (submenus[selected_idx]->Action() == MenuActionResult::Quit)
4238
0
          return eQuitApplication;
4239
0
        window.GetParent()->RemoveSubWindow(&window);
4240
0
        return eKeyHandled;
4241
0
      }
4242
0
      break;
4243
4244
0
    case KEY_ESCAPE: // Beware: pressing escape key has 1 to 2 second delay in
4245
                     // case other chars are entered for escaped sequences
4246
0
      window.GetParent()->RemoveSubWindow(&window);
4247
0
      return eKeyHandled;
4248
4249
0
    default:
4250
0
      for (size_t i = 0; i < num_submenus; ++i) {
4251
0
        Menu *menu = submenus[i].get();
4252
0
        if (menu->GetKeyValue() == key) {
4253
0
          SetSelectedSubmenuIndex(i);
4254
0
          window.GetParent()->RemoveSubWindow(&window);
4255
0
          if (menu->Action() == MenuActionResult::Quit)
4256
0
            return eQuitApplication;
4257
0
          return eKeyHandled;
4258
0
        }
4259
0
      }
4260
0
      break;
4261
0
    }
4262
0
  } else if (menu_type == Menu::Type::Separator) {
4263
0
  }
4264
0
  return result;
4265
0
}
4266
4267
class Application {
4268
public:
4269
  Application(FILE *in, FILE *out)
4270
0
      : m_window_sp(), m_screen(nullptr), m_in(in), m_out(out) {}
4271
4272
0
  ~Application() {
4273
0
    m_window_delegates.clear();
4274
0
    m_window_sp.reset();
4275
0
    if (m_screen) {
4276
0
      ::delscreen(m_screen);
4277
0
      m_screen = nullptr;
4278
0
    }
4279
0
  }
4280
4281
0
  void Initialize() {
4282
0
    m_screen = ::newterm(nullptr, m_out, m_in);
4283
0
    ::start_color();
4284
0
    ::curs_set(0);
4285
0
    ::noecho();
4286
0
    ::keypad(stdscr, TRUE);
4287
0
  }
4288
4289
0
  void Terminate() { ::endwin(); }
4290
4291
0
  void Run(Debugger &debugger) {
4292
0
    bool done = false;
4293
0
    int delay_in_tenths_of_a_second = 1;
4294
4295
    // Alas the threading model in curses is a bit lame so we need to resort
4296
    // to polling every 0.5 seconds. We could poll for stdin ourselves and
4297
    // then pass the keys down but then we need to translate all of the escape
4298
    // sequences ourselves. So we resort to polling for input because we need
4299
    // to receive async process events while in this loop.
4300
4301
0
    halfdelay(delay_in_tenths_of_a_second); // Poll using some number of
4302
                                            // tenths of seconds seconds when
4303
                                            // calling Window::GetChar()
4304
4305
0
    ListenerSP listener_sp(
4306
0
        Listener::MakeListener("lldb.IOHandler.curses.Application"));
4307
0
    ConstString broadcaster_class_process(Process::GetStaticBroadcasterClass());
4308
0
    debugger.EnableForwardEvents(listener_sp);
4309
4310
0
    m_update_screen = true;
4311
0
#if defined(__APPLE__)
4312
0
    std::deque<int> escape_chars;
4313
0
#endif
4314
4315
0
    while (!done) {
4316
0
      if (m_update_screen) {
4317
0
        m_window_sp->Draw(false);
4318
        // All windows should be calling Window::DeferredRefresh() instead of
4319
        // Window::Refresh() so we can do a single update and avoid any screen
4320
        // blinking
4321
0
        update_panels();
4322
4323
        // Cursor hiding isn't working on MacOSX, so hide it in the top left
4324
        // corner
4325
0
        m_window_sp->MoveCursor(0, 0);
4326
4327
0
        doupdate();
4328
0
        m_update_screen = false;
4329
0
      }
4330
4331
0
#if defined(__APPLE__)
4332
      // Terminal.app doesn't map its function keys correctly, F1-F4 default
4333
      // to: \033OP, \033OQ, \033OR, \033OS, so lets take care of this here if
4334
      // possible
4335
0
      int ch;
4336
0
      if (escape_chars.empty())
4337
0
        ch = m_window_sp->GetChar();
4338
0
      else {
4339
0
        ch = escape_chars.front();
4340
0
        escape_chars.pop_front();
4341
0
      }
4342
0
      if (ch == KEY_ESCAPE) {
4343
0
        int ch2 = m_window_sp->GetChar();
4344
0
        if (ch2 == 'O') {
4345
0
          int ch3 = m_window_sp->GetChar();
4346
0
          switch (ch3) {
4347
0
          case 'P':
4348
0
            ch = KEY_F(1);
4349
0
            break;
4350
0
          case 'Q':
4351
0
            ch = KEY_F(2);
4352
0
            break;
4353
0
          case 'R':
4354
0
            ch = KEY_F(3);
4355
0
            break;
4356
0
          case 'S':
4357
0
            ch = KEY_F(4);
4358
0
            break;
4359
0
          default:
4360
0
            escape_chars.push_back(ch2);
4361
0
            if (ch3 != -1)
4362
0
              escape_chars.push_back(ch3);
4363
0
            break;
4364
0
          }
4365
0
        } else if (ch2 != -1)
4366
0
          escape_chars.push_back(ch2);
4367
0
      }
4368
#else
4369
      int ch = m_window_sp->GetChar();
4370
4371
#endif
4372
0
      if (ch == -1) {
4373
0
        if (feof(m_in) || ferror(m_in)) {
4374
0
          done = true;
4375
0
        } else {
4376
          // Just a timeout from using halfdelay(), check for events
4377
0
          EventSP event_sp;
4378
0
          while (listener_sp->PeekAtNextEvent()) {
4379
0
            listener_sp->GetEvent(event_sp, std::chrono::seconds(0));
4380
4381
0
            if (event_sp) {
4382
0
              Broadcaster *broadcaster = event_sp->GetBroadcaster();
4383
0
              if (broadcaster) {
4384
                // uint32_t event_type = event_sp->GetType();
4385
0
                ConstString broadcaster_class(
4386
0
                    broadcaster->GetBroadcasterClass());
4387
0
                if (broadcaster_class == broadcaster_class_process) {
4388
0
                  m_update_screen = true;
4389
0
                  continue; // Don't get any key, just update our view
4390
0
                }
4391
0
              }
4392
0
            }
4393
0
          }
4394
0
        }
4395
0
      } else {
4396
0
        HandleCharResult key_result = m_window_sp->HandleChar(ch);
4397
0
        switch (key_result) {
4398
0
        case eKeyHandled:
4399
0
          m_update_screen = true;
4400
0
          break;
4401
0
        case eKeyNotHandled:
4402
0
          if (ch == 12) { // Ctrl+L, force full redraw
4403
0
            redrawwin(m_window_sp->get());
4404
0
            m_update_screen = true;
4405
0
          }
4406
0
          break;
4407
0
        case eQuitApplication:
4408
0
          done = true;
4409
0
          break;
4410
0
        }
4411
0
      }
4412
0
    }
4413
4414
0
    debugger.CancelForwardEvents(listener_sp);
4415
0
  }
4416
4417
0
  WindowSP &GetMainWindow() {
4418
0
    if (!m_window_sp)
4419
0
      m_window_sp = std::make_shared<Window>("main", stdscr, false);
4420
0
    return m_window_sp;
4421
0
  }
4422
4423
0
  void TerminalSizeChanged() {
4424
0
    ::endwin();
4425
0
    ::refresh();
4426
0
    Rect content_bounds = m_window_sp->GetFrame();
4427
0
    m_window_sp->SetBounds(content_bounds);
4428
0
    if (WindowSP menubar_window_sp = m_window_sp->FindSubWindow("Menubar"))
4429
0
      menubar_window_sp->SetBounds(content_bounds.MakeMenuBar());
4430
0
    if (WindowSP status_window_sp = m_window_sp->FindSubWindow("Status"))
4431
0
      status_window_sp->SetBounds(content_bounds.MakeStatusBar());
4432
4433
0
    WindowSP source_window_sp = m_window_sp->FindSubWindow("Source");
4434
0
    WindowSP variables_window_sp = m_window_sp->FindSubWindow("Variables");
4435
0
    WindowSP registers_window_sp = m_window_sp->FindSubWindow("Registers");
4436
0
    WindowSP threads_window_sp = m_window_sp->FindSubWindow("Threads");
4437
4438
0
    Rect threads_bounds;
4439
0
    Rect source_variables_bounds;
4440
0
    content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds,
4441
0
                                           threads_bounds);
4442
0
    if (threads_window_sp)
4443
0
      threads_window_sp->SetBounds(threads_bounds);
4444
0
    else
4445
0
      source_variables_bounds = content_bounds;
4446
4447
0
    Rect source_bounds;
4448
0
    Rect variables_registers_bounds;
4449
0
    source_variables_bounds.HorizontalSplitPercentage(
4450
0
        0.70, source_bounds, variables_registers_bounds);
4451
0
    if (variables_window_sp || registers_window_sp) {
4452
0
      if (variables_window_sp && registers_window_sp) {
4453
0
        Rect variables_bounds;
4454
0
        Rect registers_bounds;
4455
0
        variables_registers_bounds.VerticalSplitPercentage(
4456
0
            0.50, variables_bounds, registers_bounds);
4457
0
        variables_window_sp->SetBounds(variables_bounds);
4458
0
        registers_window_sp->SetBounds(registers_bounds);
4459
0
      } else if (variables_window_sp) {
4460
0
        variables_window_sp->SetBounds(variables_registers_bounds);
4461
0
      } else {
4462
0
        registers_window_sp->SetBounds(variables_registers_bounds);
4463
0
      }
4464
0
    } else {
4465
0
      source_bounds = source_variables_bounds;
4466
0
    }
4467
4468
0
    source_window_sp->SetBounds(source_bounds);
4469
4470
0
    touchwin(stdscr);
4471
0
    redrawwin(m_window_sp->get());
4472
0
    m_update_screen = true;
4473
0
  }
4474
4475
protected:
4476
  WindowSP m_window_sp;
4477
  WindowDelegates m_window_delegates;
4478
  SCREEN *m_screen;
4479
  FILE *m_in;
4480
  FILE *m_out;
4481
  bool m_update_screen = false;
4482
};
4483
4484
} // namespace curses
4485
4486
using namespace curses;
4487
4488
struct Row {
4489
  ValueObjectUpdater value;
4490
  Row *parent;
4491
  // The process stop ID when the children were calculated.
4492
  uint32_t children_stop_id = 0;
4493
  int row_idx = 0;
4494
  int x = 1;
4495
  int y = 1;
4496
  bool might_have_children;
4497
  bool expanded = false;
4498
  bool calculated_children = false;
4499
  std::vector<Row> children;
4500
4501
  Row(const ValueObjectSP &v, Row *p)
4502
      : value(v), parent(p),
4503
0
        might_have_children(v ? v->MightHaveChildren() : false) {}
4504
4505
0
  size_t GetDepth() const {
4506
0
    if (parent)
4507
0
      return 1 + parent->GetDepth();
4508
0
    return 0;
4509
0
  }
4510
4511
0
  void Expand() { expanded = true; }
4512
4513
0
  std::vector<Row> &GetChildren() {
4514
0
    ProcessSP process_sp = value.GetProcessSP();
4515
0
    auto stop_id = process_sp->GetStopID();
4516
0
    if (process_sp && stop_id != children_stop_id) {
4517
0
      children_stop_id = stop_id;
4518
0
      calculated_children = false;
4519
0
    }
4520
0
    if (!calculated_children) {
4521
0
      children.clear();
4522
0
      calculated_children = true;
4523
0
      ValueObjectSP valobj = value.GetSP();
4524
0
      if (valobj) {
4525
0
        const size_t num_children = valobj->GetNumChildren();
4526
0
        for (size_t i = 0; i < num_children; ++i) {
4527
0
          children.push_back(Row(valobj->GetChildAtIndex(i, true), this));
4528
0
        }
4529
0
      }
4530
0
    }
4531
0
    return children;
4532
0
  }
4533
4534
0
  void Unexpand() {
4535
0
    expanded = false;
4536
0
    calculated_children = false;
4537
0
    children.clear();
4538
0
  }
4539
4540
0
  void DrawTree(Window &window) {
4541
0
    if (parent)
4542
0
      parent->DrawTreeForChild(window, this, 0);
4543
4544
0
    if (might_have_children) {
4545
      // It we can get UTF8 characters to work we should try to use the
4546
      // "symbol" UTF8 string below
4547
      //            const char *symbol = "";
4548
      //            if (row.expanded)
4549
      //                symbol = "\xe2\x96\xbd ";
4550
      //            else
4551
      //                symbol = "\xe2\x96\xb7 ";
4552
      //            window.PutCString (symbol);
4553
4554
      // The ACS_DARROW and ACS_RARROW don't look very nice they are just a 'v'
4555
      // or '>' character...
4556
      //            if (expanded)
4557
      //                window.PutChar (ACS_DARROW);
4558
      //            else
4559
      //                window.PutChar (ACS_RARROW);
4560
      // Since we can't find any good looking right arrow/down arrow symbols,
4561
      // just use a diamond...
4562
0
      window.PutChar(ACS_DIAMOND);
4563
0
      window.PutChar(ACS_HLINE);
4564
0
    }
4565
0
  }
4566
4567
0
  void DrawTreeForChild(Window &window, Row *child, uint32_t reverse_depth) {
4568
0
    if (parent)
4569
0
      parent->DrawTreeForChild(window, this, reverse_depth + 1);
4570
4571
0
    if (&GetChildren().back() == child) {
4572
      // Last child
4573
0
      if (reverse_depth == 0) {
4574
0
        window.PutChar(ACS_LLCORNER);
4575
0
        window.PutChar(ACS_HLINE);
4576
0
      } else {
4577
0
        window.PutChar(' ');
4578
0
        window.PutChar(' ');
4579
0
      }
4580
0
    } else {
4581
0
      if (reverse_depth == 0) {
4582
0
        window.PutChar(ACS_LTEE);
4583
0
        window.PutChar(ACS_HLINE);
4584
0
      } else {
4585
0
        window.PutChar(ACS_VLINE);
4586
0
        window.PutChar(' ');
4587
0
      }
4588
0
    }
4589
0
  }
4590
};
4591
4592
struct DisplayOptions {
4593
  bool show_types;
4594
};
4595
4596
class TreeItem;
4597
4598
class TreeDelegate {
4599
public:
4600
0
  TreeDelegate() = default;
4601
0
  virtual ~TreeDelegate() = default;
4602
4603
  virtual void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) = 0;
4604
  virtual void TreeDelegateGenerateChildren(TreeItem &item) = 0;
4605
  virtual void TreeDelegateUpdateSelection(TreeItem &root, int &selection_index,
4606
0
                                           TreeItem *&selected_item) {}
4607
  // This is invoked when a tree item is selected. If true is returned, the
4608
  // views are updated.
4609
  virtual bool TreeDelegateItemSelected(TreeItem &item) = 0;
4610
0
  virtual bool TreeDelegateExpandRootByDefault() { return false; }
4611
  // This is mostly useful for root tree delegates. If false is returned,
4612
  // drawing will be skipped completely. This is needed, for instance, in
4613
  // skipping drawing of the threads tree if there is no running process.
4614
0
  virtual bool TreeDelegateShouldDraw() { return true; }
4615
};
4616
4617
typedef std::shared_ptr<TreeDelegate> TreeDelegateSP;
4618
4619
class TreeItem {
4620
public:
4621
  TreeItem(TreeItem *parent, TreeDelegate &delegate, bool might_have_children)
4622
      : m_parent(parent), m_delegate(delegate), m_user_data(nullptr),
4623
        m_identifier(0), m_row_idx(-1), m_children(),
4624
0
        m_might_have_children(might_have_children), m_is_expanded(false) {
4625
0
    if (m_parent == nullptr)
4626
0
      m_is_expanded = m_delegate.TreeDelegateExpandRootByDefault();
4627
0
  }
4628
4629
0
  TreeItem &operator=(const TreeItem &rhs) {
4630
0
    if (this != &rhs) {
4631
0
      m_parent = rhs.m_parent;
4632
0
      m_delegate = rhs.m_delegate;
4633
0
      m_user_data = rhs.m_user_data;
4634
0
      m_identifier = rhs.m_identifier;
4635
0
      m_row_idx = rhs.m_row_idx;
4636
0
      m_children = rhs.m_children;
4637
0
      m_might_have_children = rhs.m_might_have_children;
4638
0
      m_is_expanded = rhs.m_is_expanded;
4639
0
    }
4640
0
    return *this;
4641
0
  }
4642
4643
0
  TreeItem(const TreeItem &) = default;
4644
4645
0
  size_t GetDepth() const {
4646
0
    if (m_parent)
4647
0
      return 1 + m_parent->GetDepth();
4648
0
    return 0;
4649
0
  }
4650
4651
0
  int GetRowIndex() const { return m_row_idx; }
4652
4653
0
  void ClearChildren() { m_children.clear(); }
4654
4655
0
  void Resize(size_t n, const TreeItem &t) { m_children.resize(n, t); }
4656
4657
0
  TreeItem &operator[](size_t i) { return m_children[i]; }
4658
4659
0
  void SetRowIndex(int row_idx) { m_row_idx = row_idx; }
4660
4661
0
  size_t GetNumChildren() {
4662
0
    m_delegate.TreeDelegateGenerateChildren(*this);
4663
0
    return m_children.size();
4664
0
  }
4665
4666
0
  void ItemWasSelected() { m_delegate.TreeDelegateItemSelected(*this); }
4667
4668
0
  void CalculateRowIndexes(int &row_idx) {
4669
0
    SetRowIndex(row_idx);
4670
0
    ++row_idx;
4671
4672
0
    const bool expanded = IsExpanded();
4673
4674
    // The root item must calculate its children, or we must calculate the
4675
    // number of children if the item is expanded
4676
0
    if (m_parent == nullptr || expanded)
4677
0
      GetNumChildren();
4678
4679
0
    for (auto &item : m_children) {
4680
0
      if (expanded)
4681
0
        item.CalculateRowIndexes(row_idx);
4682
0
      else
4683
0
        item.SetRowIndex(-1);
4684
0
    }
4685
0
  }
4686
4687
0
  TreeItem *GetParent() { return m_parent; }
4688
4689
0
  bool IsExpanded() const { return m_is_expanded; }
4690
4691
0
  void Expand() { m_is_expanded = true; }
4692
4693
0
  void Unexpand() { m_is_expanded = false; }
4694
4695
  bool Draw(Window &window, const int first_visible_row,
4696
0
            const uint32_t selected_row_idx, int &row_idx, int &num_rows_left) {
4697
0
    if (num_rows_left <= 0)
4698
0
      return false;
4699
4700
0
    if (m_row_idx >= first_visible_row) {
4701
0
      window.MoveCursor(2, row_idx + 1);
4702
4703
0
      if (m_parent)
4704
0
        m_parent->DrawTreeForChild(window, this, 0);
4705
4706
0
      if (m_might_have_children) {
4707
        // It we can get UTF8 characters to work we should try to use the
4708
        // "symbol" UTF8 string below
4709
        //            const char *symbol = "";
4710
        //            if (row.expanded)
4711
        //                symbol = "\xe2\x96\xbd ";
4712
        //            else
4713
        //                symbol = "\xe2\x96\xb7 ";
4714
        //            window.PutCString (symbol);
4715
4716
        // The ACS_DARROW and ACS_RARROW don't look very nice they are just a
4717
        // 'v' or '>' character...
4718
        //            if (expanded)
4719
        //                window.PutChar (ACS_DARROW);
4720
        //            else
4721
        //                window.PutChar (ACS_RARROW);
4722
        // Since we can't find any good looking right arrow/down arrow symbols,
4723
        // just use a diamond...
4724
0
        window.PutChar(ACS_DIAMOND);
4725
0
        window.PutChar(ACS_HLINE);
4726
0
      }
4727
0
      bool highlight = (selected_row_idx == static_cast<size_t>(m_row_idx)) &&
4728
0
                       window.IsActive();
4729
4730
0
      if (highlight)
4731
0
        window.AttributeOn(A_REVERSE);
4732
4733
0
      m_delegate.TreeDelegateDrawTreeItem(*this, window);
4734
4735
0
      if (highlight)
4736
0
        window.AttributeOff(A_REVERSE);
4737
0
      ++row_idx;
4738
0
      --num_rows_left;
4739
0
    }
4740
4741
0
    if (num_rows_left <= 0)
4742
0
      return false; // We are done drawing...
4743
4744
0
    if (IsExpanded()) {
4745
0
      for (auto &item : m_children) {
4746
        // If we displayed all the rows and item.Draw() returns false we are
4747
        // done drawing and can exit this for loop
4748
0
        if (!item.Draw(window, first_visible_row, selected_row_idx, row_idx,
4749
0
                       num_rows_left))
4750
0
          break;
4751
0
      }
4752
0
    }
4753
0
    return num_rows_left >= 0; // Return true if not done drawing yet
4754
0
  }
4755
4756
  void DrawTreeForChild(Window &window, TreeItem *child,
4757
0
                        uint32_t reverse_depth) {
4758
0
    if (m_parent)
4759
0
      m_parent->DrawTreeForChild(window, this, reverse_depth + 1);
4760
4761
0
    if (&m_children.back() == child) {
4762
      // Last child
4763
0
      if (reverse_depth == 0) {
4764
0
        window.PutChar(ACS_LLCORNER);
4765
0
        window.PutChar(ACS_HLINE);
4766
0
      } else {
4767
0
        window.PutChar(' ');
4768
0
        window.PutChar(' ');
4769
0
      }
4770
0
    } else {
4771
0
      if (reverse_depth == 0) {
4772
0
        window.PutChar(ACS_LTEE);
4773
0
        window.PutChar(ACS_HLINE);
4774
0
      } else {
4775
0
        window.PutChar(ACS_VLINE);
4776
0
        window.PutChar(' ');
4777
0
      }
4778
0
    }
4779