blob: d4f37799a88e4bce5053c575f708ccbbb35e27fd [file] [log] [blame]
Adedeji Adebisi12c5f112021-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>
24#include <string>
25#include <vector>
26constexpr int MARGIN_BOTTOM = 1;
27class DBusTopWindow
28{
29 public:
30 DBusTopWindow()
31 {
32 win = newwin(25, 80, 0, 0); // Default to 80x25, will be updated
33 has_border_ = true;
34 focused_ = false;
35 selectable_ = true;
36 visible_ = true;
37 maximize_ = false;
38 }
39
40 virtual ~DBusTopWindow()
41 {}
42 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:
84 SummaryView() : DBusTopWindow()
85 {}
86 void Render() override;
87 void OnResize(int win_w, int win_h) override
88 {
89 rect.h = 8;
90 rect.w = win_w;
91 rect.x = 0;
92 rect.y = 0;
93 UpdateWindowSizeAndPosition();
94 }
95
96 void UpdateDBusTopStatistics(DBusTopStatistics* stat);
97 void OnKeyDown(const std::string& key) override
98 {}
99 std::string GetStatusString() override
100 {
101 return "Summary View";
102 }
103
104 private:
105 float method_call_, method_return_, signal_, error_, total_;
106};
107
108class SensorDetailView : public DBusTopWindow
109{
110 public:
111 SensorDetailView() : DBusTopWindow()
112 {
113 choice_ = -999; // -999 means invalid
114 h_padding = 2;
115 h_spacing = 3;
116 col_width = 15;
117 idx0 = idx1 = -999;
118 state = SensorList;
119 }
120
121 void Render() override;
122 int DispSensorsPerColumn()
123 {
124 return rect.h - 3;
125 }
126
127 int DispSensorsPerRow()
128 {
129 int ncols = 0;
130 while (true)
131 {
132 int next = ncols + 1;
133 int w = 2 * h_padding + col_width * next;
134 if (next > 1)
135 w += (next - 1) * h_spacing;
136 if (w <= rect.w - 2)
137 {
138 ncols = next;
139 }
140 else
141 {
142 break;
143 }
144 }
145 return ncols;
146 }
147
148 void OnKeyDown(const std::string& key) override
149 {
150 if (state == SensorList)
151 { // Currently in sensor list
152 if (key == "right")
153 {
154 MoveChoiceCursorHorizontally(1);
155 }
156 else if (key == "left")
157 {
158 MoveChoiceCursorHorizontally(-1);
159 }
160 else if (key == "up")
161 {
162 MoveChoiceCursor(-1, true);
163 }
164 else if (key == "down")
165 {
166 MoveChoiceCursor(1, true);
167 }
168 else if (key == "enter")
169 {
170 if (choice_ != -999)
171 {
172 state = SensorDetail;
173 }
174 }
175 else if (key == "escape")
176 {
177 choice_ = -999;
178 }
179 }
180 else if (state == SensorDetail)
181 { // Currently focusing on a sensor
182 if (key == "right" || key == "down")
183 {
184 MoveChoiceCursor(1, true);
185 }
186 else if (key == "left" || key == "up")
187 {
188 MoveChoiceCursor(-1, true);
189 }
190 else if (key == "escape")
191 {
192 state = SensorList;
193 }
194 }
195
196 Render(); // This window is already on top, redrawing won't corrupt
197 }
198
199 void MoveChoiceCursor(int delta, bool wrap_around = true)
200 {
201 const int ns = sensor_ids_.size();
202 if (ns < 1)
203 return;
204 // First of all, if cursor is inactive, activate it
205 if (choice_ == -999)
206 {
207 if (delta > 0)
208 {
209 choice_ = 0;
210 curr_sensor_id_ = sensor_ids_[0];
211 return;
212 }
213 else
214 {
215 choice_ = ns - 1;
216 curr_sensor_id_ = sensor_ids_.back();
217 return;
218 }
219 }
220 int choice_next = choice_ + delta;
221 while (choice_next >= ns)
222 {
223 if (wrap_around)
224 {
225 choice_next -= ns;
226 }
227 else
228 {
229 choice_next = ns - 1;
230 }
231 }
232 while (choice_next < 0)
233 {
234 if (wrap_around)
235 {
236 choice_next += ns;
237 }
238 else
239 {
240 choice_next = 0;
241 }
242 }
243 choice_ = choice_next;
244 curr_sensor_id_ = sensor_ids_[choice_];
245 }
246
247 void MoveChoiceCursorHorizontally(int delta)
248 {
249 if (delta != 0 && delta != -1 && delta != 1)
250 return;
251 const int ns = sensor_ids_.size();
252 if (ns < 1)
253 return;
254 if (choice_ == -999)
255 {
256 if (delta > 0)
257 {
258 choice_ = 0;
259 curr_sensor_id_ = sensor_ids_[0];
260 return;
261 }
262 else
263 {
264 curr_sensor_id_ = sensor_ids_.back();
265 choice_ = ns - 1;
266 return;
267 }
268 }
269 const int nrows = DispSensorsPerColumn();
270 int tot_columns = (ns - 1) / nrows + 1;
271 int num_rows_last_column = ns - nrows * (tot_columns - 1);
272 int y = choice_ % nrows, x = choice_ / nrows;
273 if (delta == 1)
274 {
275 x++;
276 }
277 else
278 {
279 x--;
280 }
281 bool overflow_to_right = false;
282 if (y < num_rows_last_column)
283 {
284 if (x >= tot_columns)
285 {
286 overflow_to_right = true;
287 }
288 }
289 else
290 {
291 if (x >= tot_columns - 1)
292 {
293 overflow_to_right = true;
294 }
295 }
296 bool overflow_to_left = false;
297 if (x < 0)
298 {
299 overflow_to_left = true;
300 }
301 {
302 if (overflow_to_right)
303 {
304 y++;
305 // overflow past the right of window
306 // Start probing next line
307 if (y >= nrows)
308 {
309 choice_ = 0;
310 return;
311 }
312 else
313 {
314 choice_ = y;
315 return;
316 }
317 }
318 else if (overflow_to_left)
319 { // overflow past the left of window
320 y--;
321 if (y < 0)
322 { // overflow past the top of window
323 // Focus on the visually bottom-right entry
324 if (num_rows_last_column == nrows)
325 { // last col fully populated
326 choice_ = ns - 1;
327 }
328 else
329 { // last column is not fully populated
330 choice_ = ns - num_rows_last_column - 1;
331 }
332 return;
333 }
334 else
335 {
336 if (y < num_rows_last_column)
337 {
338 choice_ = nrows * (tot_columns - 1) + y;
339 }
340 else
341 {
342 choice_ = nrows * (tot_columns - 2) + y;
343 }
344 }
345 }
346 else
347 {
348 choice_ = y + x * nrows;
349 }
350 }
351 curr_sensor_id_ = sensor_ids_[choice_];
352 }
353
354 // Cache the sensor list in the sensor snapshot
355 void UpdateSensorSnapshot(SensorSnapshot* snapshot)
356 {
357 std::string old_sensor_id = "";
358 if (choice_ != -999)
359 {
360 old_sensor_id = sensor_ids_[choice_];
361 }
362 std::vector<std::string> new_sensors =
363 snapshot->GetDistinctSensorNames();
364 if (new_sensors == sensor_ids_)
365 {
366 return; // Nothing is changed
367 }
368 // Assume changed
369 sensor_ids_ = new_sensors;
370 choice_ = -999;
371 for (int i = 0; i < static_cast<int>(new_sensors.size()); i++)
372 {
373 if (new_sensors[i] == old_sensor_id)
374 {
375 choice_ = i;
376 break;
377 curr_sensor_id_ = sensor_ids_[choice_];
378 }
379 }
380 }
381
382 void OnResize(int win_w, int win_h) override
383 {
384 rect.x = 0;
385 rect.y = 8 - MARGIN_BOTTOM;
386 rect.w = win_w / 2;
387 rect.h = win_h - rect.y - MARGIN_BOTTOM;
388 UpdateWindowSizeAndPosition();
389 }
390
391 std::vector<std::string> sensor_ids_;
392 // We need to keep track of the currently-selected sensor ID because
393 // the sensor ID might theoretically become invalidated at any moment, and
394 // we should allow the UI to show an error gracefully in that case.
395 std::string curr_sensor_id_;
396 int choice_;
397 int h_padding;
398 int h_spacing;
399 int col_width;
400 int idx0, idx1; // Range of sensors on display
401 enum State
402 {
403 SensorList,
404 SensorDetail,
405 };
406
407 State state;
408 std::string GetStatusString() override;
409};
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
425 int sort_col_idx_; // Column used for sorting
426 enum SortOrder
427 {
428 Ascending,
429 Descending,
430 };
431 SortOrder sort_order_;
432
433 int disp_row_idx_; // From which row to start displaying? (essentially a
434 // vertical scroll bar)
435 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 }
454
455 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
522 void OnKeyDown(const std::string& key) override
523 {}
524 void OnResize(int win_w, int win_h) override
525 {
526 rect.h = 1;
527 rect.w = win_w;
528 rect.x = 0;
529 rect.y = win_h - 1;
530 UpdateWindowSizeAndPosition();
531 }
532
533 void Render() override;
534 std::string GetStatusString() override
535 {
536 return "";
537 }
538
539};