blob: 149411a5fe393fece772ca62650330e75bf674ef [file] [log] [blame]
Adedeji Adebisi684ec912021-07-22 18:07:52 +00001// Copyright 2021 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#pragma once
16
17#include "analyzer.hpp"
18#include "bargraph.hpp"
19#include "main.hpp"
20#include "menu.hpp"
21#include "sensorhelper.hpp"
22
23#include <ncurses.h>
nitroglycerine49dcde12023-03-14 07:24:39 -070024
Adedeji Adebisi684ec912021-07-22 18:07:52 +000025#include <string>
26#include <vector>
27constexpr int MARGIN_BOTTOM = 1;
28class DBusTopWindow
29{
30 public:
31 DBusTopWindow()
32 {
33 win = newwin(25, 80, 0, 0); // Default to 80x25, will be updated
34 has_border_ = true;
35 focused_ = false;
36 selectable_ = true;
37 visible_ = true;
38 maximize_ = false;
39 }
40
Patrick Williamsf5c0b9d2023-03-14 09:31:01 -050041 virtual ~DBusTopWindow() {}
Adedeji Adebisi684ec912021-07-22 18:07:52 +000042 virtual void OnKeyDown(const std::string& key) = 0;
43 virtual void Render() = 0;
44 virtual void OnResize(int win_w, int win_h) = 0;
45 void UpdateWindowSizeAndPosition()
46 {
47 mvwin(win, rect.y, rect.x);
48 wresize(win, rect.h, rect.w);
49 }
50
51 void DrawBorderIfNeeded()
52 {
53 if (focused_)
54 {
55 wborder(win, '*', '*', '*', '*', '*', '*', '*', '*');
56 }
57 else
58 {
59 wborder(win, '|', '|', '-', '-', '+', '+', '+', '+');
60 }
61 wrefresh(win);
62 }
63
64 virtual void RecreateWindow()
65 {
66 delwin(win);
67 win = newwin(25, 80, 0, 0);
68 UpdateWindowSizeAndPosition();
69 }
70
71 virtual std::string GetStatusString() = 0;
72 WINDOW* win;
73 Rect rect;
74 bool has_border_;
75 bool focused_;
76 bool selectable_;
77 bool maximize_;
78 bool visible_;
79};
80
81class SummaryView : public DBusTopWindow
82{
83 public:
Patrick Williamsf5c0b9d2023-03-14 09:31:01 -050084 SummaryView() : DBusTopWindow() {}
Adedeji Adebisi684ec912021-07-22 18:07:52 +000085 void Render() override;
nitroglycerine49dcde12023-03-14 07:24:39 -070086 void OnResize(int win_w, [[maybe_unused]] int win_h) override
Adedeji Adebisi684ec912021-07-22 18:07:52 +000087 {
88 rect.h = 8;
89 rect.w = win_w;
90 rect.x = 0;
91 rect.y = 0;
92 UpdateWindowSizeAndPosition();
93 }
94
95 void UpdateDBusTopStatistics(DBusTopStatistics* stat);
Patrick Williamsf5c0b9d2023-03-14 09:31:01 -050096 void OnKeyDown([[maybe_unused]] const std::string& key) override {}
Adedeji Adebisi684ec912021-07-22 18:07:52 +000097 std::string GetStatusString() override
98 {
99 return "Summary View";
100 }
101
102 private:
103 float method_call_, method_return_, signal_, error_, total_;
104};
105
106class SensorDetailView : public DBusTopWindow
107{
108 public:
109 SensorDetailView() : DBusTopWindow()
110 {
111 choice_ = -999; // -999 means invalid
112 h_padding = 2;
113 h_spacing = 3;
114 col_width = 15;
115 idx0 = idx1 = -999;
116 state = SensorList;
117 }
118
119 void Render() override;
120 int DispSensorsPerColumn()
121 {
122 return rect.h - 3;
123 }
124
125 int DispSensorsPerRow()
126 {
127 int ncols = 0;
128 while (true)
129 {
130 int next = ncols + 1;
131 int w = 2 * h_padding + col_width * next;
132 if (next > 1)
133 w += (next - 1) * h_spacing;
134 if (w <= rect.w - 2)
135 {
136 ncols = next;
137 }
138 else
139 {
140 break;
141 }
142 }
143 return ncols;
144 }
145
146 void OnKeyDown(const std::string& key) override
147 {
148 if (state == SensorList)
149 { // Currently in sensor list
150 if (key == "right")
151 {
152 MoveChoiceCursorHorizontally(1);
153 }
154 else if (key == "left")
155 {
156 MoveChoiceCursorHorizontally(-1);
157 }
158 else if (key == "up")
159 {
160 MoveChoiceCursor(-1, true);
161 }
162 else if (key == "down")
163 {
164 MoveChoiceCursor(1, true);
165 }
166 else if (key == "enter")
167 {
168 if (choice_ != -999)
169 {
170 state = SensorDetail;
171 }
172 }
173 else if (key == "escape")
174 {
175 choice_ = -999;
176 }
177 }
178 else if (state == SensorDetail)
179 { // Currently focusing on a sensor
180 if (key == "right" || key == "down")
181 {
182 MoveChoiceCursor(1, true);
183 }
184 else if (key == "left" || key == "up")
185 {
186 MoveChoiceCursor(-1, true);
187 }
188 else if (key == "escape")
189 {
190 state = SensorList;
191 }
192 }
193
194 Render(); // This window is already on top, redrawing won't corrupt
195 }
196
197 void MoveChoiceCursor(int delta, bool wrap_around = true)
198 {
199 const int ns = sensor_ids_.size();
200 if (ns < 1)
201 return;
202 // First of all, if cursor is inactive, activate it
203 if (choice_ == -999)
204 {
205 if (delta > 0)
206 {
207 choice_ = 0;
208 curr_sensor_id_ = sensor_ids_[0];
209 return;
210 }
211 else
212 {
213 choice_ = ns - 1;
214 curr_sensor_id_ = sensor_ids_.back();
215 return;
216 }
217 }
218 int choice_next = choice_ + delta;
219 while (choice_next >= ns)
220 {
221 if (wrap_around)
222 {
223 choice_next -= ns;
224 }
225 else
226 {
227 choice_next = ns - 1;
228 }
229 }
230 while (choice_next < 0)
231 {
232 if (wrap_around)
233 {
234 choice_next += ns;
235 }
236 else
237 {
238 choice_next = 0;
239 }
240 }
241 choice_ = choice_next;
242 curr_sensor_id_ = sensor_ids_[choice_];
243 }
244
245 void MoveChoiceCursorHorizontally(int delta)
246 {
247 if (delta != 0 && delta != -1 && delta != 1)
248 return;
249 const int ns = sensor_ids_.size();
250 if (ns < 1)
251 return;
252 if (choice_ == -999)
253 {
254 if (delta > 0)
255 {
256 choice_ = 0;
257 curr_sensor_id_ = sensor_ids_[0];
258 return;
259 }
260 else
261 {
262 curr_sensor_id_ = sensor_ids_.back();
263 choice_ = ns - 1;
264 return;
265 }
266 }
267 const int nrows = DispSensorsPerColumn();
268 int tot_columns = (ns - 1) / nrows + 1;
269 int num_rows_last_column = ns - nrows * (tot_columns - 1);
270 int y = choice_ % nrows, x = choice_ / nrows;
271 if (delta == 1)
272 {
273 x++;
274 }
275 else
276 {
277 x--;
278 }
279 bool overflow_to_right = false;
280 if (y < num_rows_last_column)
281 {
282 if (x >= tot_columns)
283 {
284 overflow_to_right = true;
285 }
286 }
287 else
288 {
289 if (x >= tot_columns - 1)
290 {
291 overflow_to_right = true;
292 }
293 }
294 bool overflow_to_left = false;
295 if (x < 0)
296 {
297 overflow_to_left = true;
298 }
299 {
300 if (overflow_to_right)
301 {
302 y++;
303 // overflow past the right of window
304 // Start probing next line
305 if (y >= nrows)
306 {
307 choice_ = 0;
308 return;
309 }
310 else
311 {
312 choice_ = y;
313 return;
314 }
315 }
316 else if (overflow_to_left)
317 { // overflow past the left of window
318 y--;
319 if (y < 0)
320 { // overflow past the top of window
321 // Focus on the visually bottom-right entry
322 if (num_rows_last_column == nrows)
323 { // last col fully populated
324 choice_ = ns - 1;
325 }
326 else
327 { // last column is not fully populated
328 choice_ = ns - num_rows_last_column - 1;
329 }
330 return;
331 }
332 else
333 {
334 if (y < num_rows_last_column)
335 {
336 choice_ = nrows * (tot_columns - 1) + y;
337 }
338 else
339 {
340 choice_ = nrows * (tot_columns - 2) + y;
341 }
342 }
343 }
344 else
345 {
346 choice_ = y + x * nrows;
347 }
348 }
349 curr_sensor_id_ = sensor_ids_[choice_];
350 }
351
352 // Cache the sensor list in the sensor snapshot
353 void UpdateSensorSnapshot(SensorSnapshot* snapshot)
354 {
355 std::string old_sensor_id = "";
356 if (choice_ != -999)
357 {
358 old_sensor_id = sensor_ids_[choice_];
359 }
360 std::vector<std::string> new_sensors =
361 snapshot->GetDistinctSensorNames();
362 if (new_sensors == sensor_ids_)
363 {
364 return; // Nothing is changed
365 }
366 // Assume changed
367 sensor_ids_ = new_sensors;
368 choice_ = -999;
369 for (int i = 0; i < static_cast<int>(new_sensors.size()); i++)
370 {
371 if (new_sensors[i] == old_sensor_id)
372 {
373 choice_ = i;
374 break;
375 curr_sensor_id_ = sensor_ids_[choice_];
376 }
377 }
378 }
379
380 void OnResize(int win_w, int win_h) override
381 {
382 rect.x = 0;
383 rect.y = 8 - MARGIN_BOTTOM;
384 rect.w = win_w / 2;
385 rect.h = win_h - rect.y - MARGIN_BOTTOM;
386 UpdateWindowSizeAndPosition();
387 }
388
389 std::vector<std::string> sensor_ids_;
390 // We need to keep track of the currently-selected sensor ID because
391 // the sensor ID might theoretically become invalidated at any moment, and
392 // we should allow the UI to show an error gracefully in that case.
393 std::string curr_sensor_id_;
394 int choice_;
395 int h_padding;
396 int h_spacing;
397 int col_width;
398 int idx0, idx1; // Range of sensors on display
399 enum State
400 {
401 SensorList,
402 SensorDetail,
403 };
404
405 State state;
406 std::string GetStatusString() override;
407};
408
409class DBusStatListView : public DBusTopWindow
410{
411 public:
412 DBusStatListView();
413 void Render() override;
414 void OnResize(int win_w, int win_h) override;
415 void OnKeyDown(const std::string& key) override;
416 int horizontal_pan_;
417 int menu_h_, menu_w_, menu_margin_;
418 ArrowKeyNavigationMenu* menu1_;
419 ArrowKeyNavigationMenu* menu2_;
420 int highlight_col_idx_; // Currently highlighted column
421 int row_idx_; // Currently highlighted row
422
423 int sort_col_idx_; // Column used for sorting
424 enum SortOrder
425 {
426 Ascending,
427 Descending,
428 };
429 SortOrder sort_order_;
430
nitroglycerine49dcde12023-03-14 07:24:39 -0700431 int disp_row_idx_; // From which row to start displaying? (essentially a
432 // vertical scroll bar)
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000433 int last_choices_[2]; // Last choice index on either side
434 enum MenuState
435 {
436 Hidden,
437 LeftSide, // When the user is choosing an entry on the left side
438 RightSide, // When the user is choosing an entry on the right side
439 };
440
441 std::vector<DBusTopSortField> GetSortFields();
442 MenuState curr_menu_state_, last_menu_state_;
443 std::string GetStatusString() override;
444 void RecreateWindow()
445 {
446 delwin(win);
447 win = newwin(25, 80, 0, 0);
448 menu1_->win_ = win;
449 menu2_->win_ = win;
450 UpdateWindowSizeAndPosition();
451 }
nitroglycerine49dcde12023-03-14 07:24:39 -0700452
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000453 private:
454 void SetMenuState(MenuState s)
455 {
456 last_menu_state_ = curr_menu_state_;
457 // Moving out from a certain side: save the last choice of that side
458 switch (curr_menu_state_)
459 {
460 case LeftSide:
461 if (s == RightSide)
462 {
463 last_choices_[0] = menu1_->Choice();
464 menu1_->Deselect();
465 }
466 break;
467 case RightSide:
468 if (s == LeftSide)
469 {
470 last_choices_[1] = menu2_->Choice();
471 menu2_->Deselect();
472 }
473 break;
474 default:
475 break;
476 }
477 // Moving into a certain side: save the cursor
478 switch (s)
479 {
480 case LeftSide:
481 if (!menu1_->Empty())
482 {
483 menu1_->SetChoiceAndConstrain(last_choices_[0]);
484 }
485 break;
486 case RightSide:
487 if (!menu2_->Empty())
488 {
489 menu2_->SetChoiceAndConstrain(last_choices_[1]);
490 }
491 break;
492 default:
493 break;
494 }
495 curr_menu_state_ = s;
496 }
497 void PanViewportOrMoveHighlightedColumn(const int delta_x);
498 // ColumnHeaders and ColumnWidths are the actual column widths used for
499 // display. They are "msg/s" or "I2c/s" prepended to the chosen set of
500 // fields.
501 std::vector<std::string> ColumnHeaders();
502 std::vector<int> ColumnWidths();
503 // X span, for checking visibility
504 std::pair<int, int> GetXSpanForColumn(const int col_idx);
505 bool IsXSpanVisible(const std::pair<int, int>& xs,
506 const int tolerance); // uses horizontal_pan_
507 std::vector<std::string> visible_columns_;
508 std::unordered_map<std::string, int> column_widths_;
509 std::map<std::vector<std::string>, DBusTopComputedMetrics> stats_snapshot_;
510};
511
512class FooterView : public DBusTopWindow
513{
514 public:
515 FooterView() : DBusTopWindow()
516 {
517 selectable_ = false; // Cannot be selected by the tab key
518 }
519
Patrick Williamsf5c0b9d2023-03-14 09:31:01 -0500520 void OnKeyDown([[maybe_unused]] const std::string& key) override {}
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000521 void OnResize(int win_w, int win_h) override
522 {
523 rect.h = 1;
524 rect.w = win_w;
525 rect.x = 0;
526 rect.y = win_h - 1;
527 UpdateWindowSizeAndPosition();
528 }
529
530 void Render() override;
531 std::string GetStatusString() override
532 {
533 return "";
534 }
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000535};