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