dbus-top: initial commits
This commit covers the basic functionalities of the dbus-top tool.
The UI is divided into 3 windows as follows:
+--------------------------+ Window list
| Window A | A: Summary statistics
+------------+-------------+ B: Sensor list or detail
| Window B | Window C | C: Detailed statistics
+------------+-------------+
To navigate the UI:
* Use tab to navigate each window
When a window is highlighted:
In Window B:
* Press esc key 3 times to leave the current sensor selection
In Window C:
* Press [Enter] to show/hide pop-up menu for column selectio
* Press [Left] to move highlight cursor to the left
* Press [Right] to move highlight cursor to the right
* Press [A] to sort by the highlighted column in ascending order
* Press [D] to sort by the highlighted column in descending order
To add recipe to Yocto and build the recipe:
1) Copy and paste the content of the .bb file into a folder that can be
detected by bitbake, such as meta-phosphor/recipes-phosphor/ipmi.
2) run "devtool modify -n dbus-top (path_to_openbmc_tools)/dbus-top/".
Signed-off-by: Adedeji Adebisi <adedejiadebisi01@gmail.com>
Change-Id: Id58ba30b815cfd9d18f54cf477d749dbdbc4545b
diff --git a/dbus-top/menu.cpp b/dbus-top/menu.cpp
new file mode 100644
index 0000000..af59f14
--- /dev/null
+++ b/dbus-top/menu.cpp
@@ -0,0 +1,322 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "menu.hpp"
+
+#include "views.hpp"
+
+ArrowKeyNavigationMenu::ArrowKeyNavigationMenu(DBusTopWindow* view) :
+ win_(view->win), parent_(view), idx0_(INVALID), idx1_(INVALID),
+ h_padding_(2), col_width_(15), h_spacing_(2), choice_(INVALID)
+{}
+
+void ArrowKeyNavigationMenu::do_Render(bool is_column_major)
+{
+ const int nrows = DispEntriesPerColumn();
+ const int ncols = DispEntriesPerRow();
+ const int items_per_page = nrows * ncols;
+ if (items_per_page < 1)
+ return;
+ int tot_num_items = items_.size();
+ // int tot_num_columns = (tot_num_items - 1) / nrows + 1;
+ // Determine whether cursor is outside the current rectangular viewport
+ bool is_cursor_out_of_view = false;
+ if (idx0_ > choice_ || idx1_ <= choice_)
+ {
+ is_cursor_out_of_view = true;
+ }
+ if (idx0_ == INVALID || idx1_ == INVALID)
+ {
+ is_cursor_out_of_view = true;
+ }
+ // Scroll the viewport such that it contains the cursor
+ if (is_cursor_out_of_view)
+ {
+ idx0_ = 0;
+ idx1_ = items_per_page;
+ }
+ while (idx1_ <= choice_)
+ {
+ if (is_column_major)
+ {
+ idx0_ += nrows;
+ idx1_ += nrows;
+ }
+ else
+ {
+ idx0_ += ncols;
+ idx1_ += ncols;
+ }
+ }
+ int y0 = rect_.y, x0 = rect_.x;
+ int y = y0, x = x0;
+ for (int i = 0; i < items_per_page; i++)
+ {
+ int idx = idx0_ + i;
+ if (idx < tot_num_items)
+ {
+ if (idx == choice_)
+ {
+ wattrset(win_, A_REVERSE);
+ }
+ std::string s = items_[idx];
+ while (s.size() < col_width_)
+ {
+ s.push_back(' ');
+ }
+ mvwprintw(win_, y, x, s.c_str());
+ wattrset(win_, 0);
+ }
+ else
+ {
+ break;
+ }
+ if (is_column_major)
+ {
+ y++;
+ if (i % nrows == nrows - 1)
+ {
+ y = y0;
+ x += col_width_ + h_spacing_;
+ }
+ }
+ else
+ {
+ x += col_width_ + h_spacing_;
+ if (i % ncols == ncols - 1)
+ {
+ x = x0;
+ y++;
+ }
+ }
+ }
+}
+
+void ArrowKeyNavigationMenu::Render()
+{
+ do_Render(order == ColumnMajor);
+}
+
+void ArrowKeyNavigationMenu::OnKeyDown(const std::string& key)
+{
+ switch (order)
+ {
+ case ColumnMajor:
+ if (key == "up")
+ {
+ MoveCursorAlongPrimaryAxis(-1);
+ }
+ else if (key == "down")
+ {
+ MoveCursorAlongPrimaryAxis(1);
+ }
+ else if (key == "left")
+ {
+ MoveCursorAlongSecondaryAxis(-1);
+ }
+ else if (key == "right")
+ {
+ MoveCursorAlongSecondaryAxis(1);
+ }
+ break;
+ case RowMajor:
+ if (key == "up")
+ {
+ MoveCursorAlongSecondaryAxis(-1);
+ }
+ else if (key == "down")
+ {
+ MoveCursorAlongSecondaryAxis(1);
+ }
+ else if (key == "left")
+ {
+ MoveCursorAlongPrimaryAxis(-1);
+ }
+ else if (key == "right")
+ {
+ MoveCursorAlongPrimaryAxis(1);
+ }
+ break;
+ break;
+ }
+}
+
+void ArrowKeyNavigationMenu::MoveCursorAlongPrimaryAxis(int delta)
+{
+ const int N = items_.size();
+ if (N < 1)
+ return;
+ // If the cursor is inactive, activate it
+ if (choice_ == INVALID)
+ {
+ if (delta > 0)
+ {
+ choice_ = 0;
+ }
+ else
+ {
+ choice_ = N - 1;
+ }
+ return;
+ }
+ int choice_next = choice_ + delta;
+ while (choice_next >= N)
+ {
+ choice_next -= N;
+ }
+ while (choice_next < 0)
+ {
+ choice_next += N;
+ }
+ choice_ = choice_next;
+}
+
+void ArrowKeyNavigationMenu::MoveCursorAlongSecondaryAxis(int delta)
+{
+ if (delta != 0 && delta != 1 && delta != -1)
+ return;
+ const int N = items_.size();
+ if (N < 1)
+ return;
+ // If the cursor is inactive, activate it
+ if (choice_ == INVALID)
+ {
+ if (delta > 0)
+ {
+ choice_ = 0;
+ }
+ else
+ {
+ choice_ = N - 1;
+ }
+ return;
+ }
+ const int nrows =
+ (order == ColumnMajor) ? DispEntriesPerColumn() : DispEntriesPerRow();
+ const int tot_columns = (N - 1) / nrows + 1;
+ const int num_rows_last_column = N - nrows * (tot_columns - 1);
+ int y = choice_ % nrows, x = choice_ / nrows;
+ if (delta == 1)
+ {
+ x++;
+ }
+ else
+ {
+ x--;
+ }
+ bool overflow_to_right = false;
+ if (y < num_rows_last_column && x >= tot_columns)
+ {
+ overflow_to_right = true;
+ }
+ if (y >= num_rows_last_column && x >= tot_columns - 1)
+ {
+ overflow_to_right = true;
+ }
+ bool overflow_to_left = false;
+ if (x < 0)
+ {
+ overflow_to_left = true;
+ }
+ if (overflow_to_right)
+ {
+ y++;
+ if (y >= nrows)
+ {
+ choice_ = 0;
+ return;
+ }
+ else
+ {
+ choice_ = y;
+ return;
+ }
+ }
+ else if (overflow_to_left)
+ {
+ y--;
+ if (y < 0)
+ {
+ if (num_rows_last_column == nrows)
+ {
+ choice_ = N - 1;
+ }
+ else
+ {
+ choice_ = N - num_rows_last_column - 1;
+ }
+ return;
+ }
+ else
+ {
+ if (y < num_rows_last_column)
+ {
+ choice_ = nrows * (tot_columns - 1) + y;
+ }
+ else
+ {
+ choice_ = nrows * (tot_columns - 2) + y;
+ }
+ }
+ }
+ else
+ {
+ choice_ = y + x * nrows;
+ }
+}
+
+void ArrowKeyNavigationMenu::SetChoiceAndConstrain(int c)
+{
+ if (Empty())
+ {
+ choice_ = INVALID;
+ return;
+ }
+ if (c < 0)
+ c = 0;
+ if (c >= static_cast<int>(items_.size()))
+ {
+ c = static_cast<int>(items_.size() - 1);
+ }
+ choice_ = c;
+}
+
+void ArrowKeyNavigationMenu::AddItem(const std::string& s)
+{
+ items_.push_back(s);
+}
+
+bool ArrowKeyNavigationMenu::RemoveHighlightedItem(std::string* ret)
+{
+ if (choice_ < 0 || choice_ >= items_.size())
+ return false;
+ std::string r = items_[choice_];
+ items_.erase(items_.begin() + choice_);
+ if (items_.empty())
+ {
+ Deselect();
+ }
+ else
+ {
+ if (choice_ >= items_.size())
+ {
+ choice_ = items_.size() - 1;
+ }
+ }
+ if (ret)
+ {
+ *ret = r;
+ }
+ return true;
+}
\ No newline at end of file