blob: a516c5f5524b7cab1bc08cc066b45aa73ab3e576 [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#include "menu.hpp"
16
17#include "views.hpp"
18
19ArrowKeyNavigationMenu::ArrowKeyNavigationMenu(DBusTopWindow* view) :
nitroglycerine49dcde12023-03-14 07:24:39 -070020 win_(view->win), h_padding_(2), col_width_(15), h_spacing_(2),
21 idx0_(INVALID), idx1_(INVALID), choice_(INVALID), parent_(view)
Adedeji Adebisi684ec912021-07-22 18:07:52 +000022{}
23
24void ArrowKeyNavigationMenu::do_Render(bool is_column_major)
25{
26 const int nrows = DispEntriesPerColumn();
27 const int ncols = DispEntriesPerRow();
28 const int items_per_page = nrows * ncols;
29 if (items_per_page < 1)
30 return;
31 int tot_num_items = items_.size();
32 // int tot_num_columns = (tot_num_items - 1) / nrows + 1;
33 // Determine whether cursor is outside the current rectangular viewport
34 bool is_cursor_out_of_view = false;
35 if (idx0_ > choice_ || idx1_ <= choice_)
36 {
37 is_cursor_out_of_view = true;
38 }
39 if (idx0_ == INVALID || idx1_ == INVALID)
40 {
41 is_cursor_out_of_view = true;
42 }
43 // Scroll the viewport such that it contains the cursor
44 if (is_cursor_out_of_view)
45 {
46 idx0_ = 0;
47 idx1_ = items_per_page;
48 }
49 while (idx1_ <= choice_)
50 {
51 if (is_column_major)
52 {
53 idx0_ += nrows;
54 idx1_ += nrows;
55 }
56 else
57 {
58 idx0_ += ncols;
59 idx1_ += ncols;
60 }
61 }
62 int y0 = rect_.y, x0 = rect_.x;
63 int y = y0, x = x0;
64 for (int i = 0; i < items_per_page; i++)
65 {
66 int idx = idx0_ + i;
67 if (idx < tot_num_items)
68 {
69 if (idx == choice_)
70 {
71 wattrset(win_, A_REVERSE);
72 }
73 std::string s = items_[idx];
nitroglycerine49dcde12023-03-14 07:24:39 -070074 while (s.size() < static_cast<size_t>(col_width_))
Adedeji Adebisi684ec912021-07-22 18:07:52 +000075 {
76 s.push_back(' ');
77 }
Sui Chen8643b5d2022-08-14 11:56:30 -070078 mvwaddstr(win_, y, x, s.c_str());
Adedeji Adebisi684ec912021-07-22 18:07:52 +000079 wattrset(win_, 0);
80 }
81 else
82 {
83 break;
84 }
85 if (is_column_major)
86 {
87 y++;
88 if (i % nrows == nrows - 1)
89 {
90 y = y0;
91 x += col_width_ + h_spacing_;
92 }
93 }
94 else
95 {
96 x += col_width_ + h_spacing_;
97 if (i % ncols == ncols - 1)
98 {
99 x = x0;
100 y++;
101 }
102 }
103 }
104}
105
106void ArrowKeyNavigationMenu::Render()
107{
108 do_Render(order == ColumnMajor);
109}
110
111void ArrowKeyNavigationMenu::OnKeyDown(const std::string& key)
112{
113 switch (order)
114 {
115 case ColumnMajor:
116 if (key == "up")
117 {
118 MoveCursorAlongPrimaryAxis(-1);
119 }
120 else if (key == "down")
121 {
122 MoveCursorAlongPrimaryAxis(1);
123 }
124 else if (key == "left")
125 {
126 MoveCursorAlongSecondaryAxis(-1);
127 }
128 else if (key == "right")
129 {
130 MoveCursorAlongSecondaryAxis(1);
131 }
132 break;
133 case RowMajor:
134 if (key == "up")
135 {
136 MoveCursorAlongSecondaryAxis(-1);
137 }
138 else if (key == "down")
139 {
140 MoveCursorAlongSecondaryAxis(1);
141 }
142 else if (key == "left")
143 {
144 MoveCursorAlongPrimaryAxis(-1);
145 }
146 else if (key == "right")
147 {
148 MoveCursorAlongPrimaryAxis(1);
149 }
150 break;
151 break;
152 }
153}
154
155void ArrowKeyNavigationMenu::MoveCursorAlongPrimaryAxis(int delta)
156{
157 const int N = items_.size();
158 if (N < 1)
159 return;
160 // If the cursor is inactive, activate it
161 if (choice_ == INVALID)
162 {
163 if (delta > 0)
164 {
165 choice_ = 0;
166 }
167 else
168 {
169 choice_ = N - 1;
170 }
171 return;
172 }
173 int choice_next = choice_ + delta;
174 while (choice_next >= N)
175 {
176 choice_next -= N;
177 }
178 while (choice_next < 0)
179 {
180 choice_next += N;
181 }
182 choice_ = choice_next;
183}
184
185void ArrowKeyNavigationMenu::MoveCursorAlongSecondaryAxis(int delta)
186{
187 if (delta != 0 && delta != 1 && delta != -1)
188 return;
189 const int N = items_.size();
190 if (N < 1)
191 return;
192 // If the cursor is inactive, activate it
193 if (choice_ == INVALID)
194 {
195 if (delta > 0)
196 {
197 choice_ = 0;
198 }
199 else
200 {
201 choice_ = N - 1;
202 }
203 return;
204 }
Patrick Williamsf5c0b9d2023-03-14 09:31:01 -0500205 const int nrows = (order == ColumnMajor) ? DispEntriesPerColumn()
206 : DispEntriesPerRow();
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000207 const int tot_columns = (N - 1) / nrows + 1;
208 const int num_rows_last_column = N - nrows * (tot_columns - 1);
209 int y = choice_ % nrows, x = choice_ / nrows;
210 if (delta == 1)
211 {
212 x++;
213 }
214 else
215 {
216 x--;
217 }
218 bool overflow_to_right = false;
219 if (y < num_rows_last_column && x >= tot_columns)
220 {
221 overflow_to_right = true;
222 }
223 if (y >= num_rows_last_column && x >= tot_columns - 1)
224 {
225 overflow_to_right = true;
226 }
227 bool overflow_to_left = false;
228 if (x < 0)
229 {
230 overflow_to_left = true;
231 }
232 if (overflow_to_right)
233 {
234 y++;
235 if (y >= nrows)
236 {
237 choice_ = 0;
238 return;
239 }
240 else
241 {
242 choice_ = y;
243 return;
244 }
245 }
246 else if (overflow_to_left)
247 {
248 y--;
249 if (y < 0)
250 {
251 if (num_rows_last_column == nrows)
252 {
253 choice_ = N - 1;
254 }
255 else
256 {
257 choice_ = N - num_rows_last_column - 1;
258 }
259 return;
260 }
261 else
262 {
263 if (y < num_rows_last_column)
264 {
265 choice_ = nrows * (tot_columns - 1) + y;
266 }
267 else
268 {
269 choice_ = nrows * (tot_columns - 2) + y;
270 }
271 }
272 }
273 else
274 {
275 choice_ = y + x * nrows;
276 }
277}
278
279void ArrowKeyNavigationMenu::SetChoiceAndConstrain(int c)
280{
281 if (Empty())
282 {
283 choice_ = INVALID;
284 return;
285 }
nitroglycerine49dcde12023-03-14 07:24:39 -0700286 if (static_cast<size_t>(c) >= items_.size())
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000287 {
nitroglycerine49dcde12023-03-14 07:24:39 -0700288 c = items_.size() - 1;
289 }
290 if (c < 0)
291 {
292 choice_ = 0;
293 return;
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000294 }
295 choice_ = c;
296}
297
298void ArrowKeyNavigationMenu::AddItem(const std::string& s)
299{
300 items_.push_back(s);
301}
302
303bool ArrowKeyNavigationMenu::RemoveHighlightedItem(std::string* ret)
304{
nitroglycerine49dcde12023-03-14 07:24:39 -0700305 if (choice_ < 0 || static_cast<size_t>(choice_) >= items_.size())
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000306 return false;
307 std::string r = items_[choice_];
308 items_.erase(items_.begin() + choice_);
309 if (items_.empty())
310 {
311 Deselect();
312 }
313 else
314 {
nitroglycerine49dcde12023-03-14 07:24:39 -0700315 if (choice_ > 0 && static_cast<size_t>(choice_) >= items_.size())
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000316 {
317 choice_ = items_.size() - 1;
318 }
319 }
320 if (ret)
321 {
322 *ret = r;
323 }
324 return true;
Sui Chen8643b5d2022-08-14 11:56:30 -0700325}