blob: a516c5f5524b7cab1bc08cc066b45aa73ab3e576 [file] [log] [blame]
// 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), h_padding_(2), col_width_(15), h_spacing_(2),
idx0_(INVALID), idx1_(INVALID), choice_(INVALID), parent_(view)
{}
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() < static_cast<size_t>(col_width_))
{
s.push_back(' ');
}
mvwaddstr(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 (static_cast<size_t>(c) >= items_.size())
{
c = items_.size() - 1;
}
if (c < 0)
{
choice_ = 0;
return;
}
choice_ = c;
}
void ArrowKeyNavigationMenu::AddItem(const std::string& s)
{
items_.push_back(s);
}
bool ArrowKeyNavigationMenu::RemoveHighlightedItem(std::string* ret)
{
if (choice_ < 0 || static_cast<size_t>(choice_) >= items_.size())
return false;
std::string r = items_[choice_];
items_.erase(items_.begin() + choice_);
if (items_.empty())
{
Deselect();
}
else
{
if (choice_ > 0 && static_cast<size_t>(choice_) >= items_.size())
{
choice_ = items_.size() - 1;
}
}
if (ret)
{
*ret = r;
}
return true;
}