blob: c74989ce3f48524725626357ae1985e8b0913e3c [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
Sui Chen8c5208f2023-04-21 14:10:05 -0700352 // Make a copy of the SensorSnapshot object for display usage
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000353 void UpdateSensorSnapshot(SensorSnapshot* snapshot)
354 {
Sui Chen8c5208f2023-04-21 14:10:05 -0700355 sensor_snapshot_ = *snapshot;
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000356 std::string old_sensor_id = "";
357 if (choice_ != -999)
358 {
359 old_sensor_id = sensor_ids_[choice_];
360 }
361 std::vector<std::string> new_sensors =
362 snapshot->GetDistinctSensorNames();
363 if (new_sensors == sensor_ids_)
364 {
365 return; // Nothing is changed
366 }
367 // Assume changed
368 sensor_ids_ = new_sensors;
369 choice_ = -999;
370 for (int i = 0; i < static_cast<int>(new_sensors.size()); i++)
371 {
372 if (new_sensors[i] == old_sensor_id)
373 {
374 choice_ = i;
375 break;
376 curr_sensor_id_ = sensor_ids_[choice_];
377 }
378 }
379 }
380
381 void OnResize(int win_w, int win_h) override
382 {
383 rect.x = 0;
384 rect.y = 8 - MARGIN_BOTTOM;
385 rect.w = win_w / 2;
386 rect.h = win_h - rect.y - MARGIN_BOTTOM;
387 UpdateWindowSizeAndPosition();
388 }
389
390 std::vector<std::string> sensor_ids_;
391 // We need to keep track of the currently-selected sensor ID because
392 // the sensor ID might theoretically become invalidated at any moment, and
393 // we should allow the UI to show an error gracefully in that case.
394 std::string curr_sensor_id_;
395 int choice_;
396 int h_padding;
397 int h_spacing;
398 int col_width;
399 int idx0, idx1; // Range of sensors on display
400 enum State
401 {
402 SensorList,
403 SensorDetail,
404 };
405
406 State state;
407 std::string GetStatusString() override;
Sui Chen8c5208f2023-04-21 14:10:05 -0700408 SensorSnapshot sensor_snapshot_;
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000409};
410
411class DBusStatListView : public DBusTopWindow
412{
413 public:
414 DBusStatListView();
415 void Render() override;
416 void OnResize(int win_w, int win_h) override;
417 void OnKeyDown(const std::string& key) override;
418 int horizontal_pan_;
419 int menu_h_, menu_w_, menu_margin_;
420 ArrowKeyNavigationMenu* menu1_;
421 ArrowKeyNavigationMenu* menu2_;
422 int highlight_col_idx_; // Currently highlighted column
423 int row_idx_; // Currently highlighted row
424
Patrick Williamsc3aa2a82023-05-10 07:51:38 -0500425 int sort_col_idx_; // Column used for sorting
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000426 enum SortOrder
427 {
428 Ascending,
429 Descending,
430 };
431 SortOrder sort_order_;
432
nitroglycerine49dcde12023-03-14 07:24:39 -0700433 int disp_row_idx_; // From which row to start displaying? (essentially a
434 // vertical scroll bar)
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000435 int last_choices_[2]; // Last choice index on either side
436 enum MenuState
437 {
438 Hidden,
439 LeftSide, // When the user is choosing an entry on the left side
440 RightSide, // When the user is choosing an entry on the right side
441 };
442
443 std::vector<DBusTopSortField> GetSortFields();
444 MenuState curr_menu_state_, last_menu_state_;
445 std::string GetStatusString() override;
446 void RecreateWindow()
447 {
448 delwin(win);
449 win = newwin(25, 80, 0, 0);
450 menu1_->win_ = win;
451 menu2_->win_ = win;
452 UpdateWindowSizeAndPosition();
453 }
nitroglycerine49dcde12023-03-14 07:24:39 -0700454
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000455 private:
456 void SetMenuState(MenuState s)
457 {
458 last_menu_state_ = curr_menu_state_;
459 // Moving out from a certain side: save the last choice of that side
460 switch (curr_menu_state_)
461 {
462 case LeftSide:
463 if (s == RightSide)
464 {
465 last_choices_[0] = menu1_->Choice();
466 menu1_->Deselect();
467 }
468 break;
469 case RightSide:
470 if (s == LeftSide)
471 {
472 last_choices_[1] = menu2_->Choice();
473 menu2_->Deselect();
474 }
475 break;
476 default:
477 break;
478 }
479 // Moving into a certain side: save the cursor
480 switch (s)
481 {
482 case LeftSide:
483 if (!menu1_->Empty())
484 {
485 menu1_->SetChoiceAndConstrain(last_choices_[0]);
486 }
487 break;
488 case RightSide:
489 if (!menu2_->Empty())
490 {
491 menu2_->SetChoiceAndConstrain(last_choices_[1]);
492 }
493 break;
494 default:
495 break;
496 }
497 curr_menu_state_ = s;
498 }
499 void PanViewportOrMoveHighlightedColumn(const int delta_x);
500 // ColumnHeaders and ColumnWidths are the actual column widths used for
501 // display. They are "msg/s" or "I2c/s" prepended to the chosen set of
502 // fields.
503 std::vector<std::string> ColumnHeaders();
504 std::vector<int> ColumnWidths();
505 // X span, for checking visibility
506 std::pair<int, int> GetXSpanForColumn(const int col_idx);
507 bool IsXSpanVisible(const std::pair<int, int>& xs,
508 const int tolerance); // uses horizontal_pan_
509 std::vector<std::string> visible_columns_;
510 std::unordered_map<std::string, int> column_widths_;
511 std::map<std::vector<std::string>, DBusTopComputedMetrics> stats_snapshot_;
512};
513
514class FooterView : public DBusTopWindow
515{
516 public:
517 FooterView() : DBusTopWindow()
518 {
519 selectable_ = false; // Cannot be selected by the tab key
520 }
521
Patrick Williamsf5c0b9d2023-03-14 09:31:01 -0500522 void OnKeyDown([[maybe_unused]] const std::string& key) override {}
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000523 void OnResize(int win_w, int win_h) override
524 {
525 rect.h = 1;
526 rect.w = win_w;
527 rect.x = 0;
528 rect.y = win_h - 1;
529 UpdateWindowSizeAndPosition();
530 }
531
532 void Render() override;
533 std::string GetStatusString() override
534 {
535 return "";
536 }
Sui Chen8c5208f2023-04-21 14:10:05 -0700537
538 void SetStatusString(const std::string& s)
539 {
540 status_string_ = s;
541 }
542 std::string status_string_;
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000543};