1 /***************************************************************************
2  *   Copyright (C) 2008-2021 by Andrzej Rybczak                            *
3  *   andrzej@rybczak.net                                                   *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) any later version.                                   *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.              *
19  ***************************************************************************/
20 
21 #ifndef NCMPCPP_MENU_IMPL_H
22 #define NCMPCPP_MENU_IMPL_H
23 
24 #include "menu.h"
25 
26 namespace NC {
27 
28 template <typename ItemT>
Menu()29 Menu<ItemT>::Menu()
30 {
31 	m_items = &m_all_items;
32 }
33 
34 template <typename ItemT>
Menu(size_t startx,size_t starty,size_t width,size_t height,const std::string & title,Color color,Border border)35 Menu<ItemT>::Menu(size_t startx,
36                   size_t starty,
37                   size_t width,
38                   size_t height,
39                   const std::string &title,
40                   Color color,
41                   Border border)
42 	: Window(startx, starty, width, height, title, color, border)
43 	, m_item_displayer(nullptr)
44 	, m_filter_predicate(nullptr)
45 	, m_beginning(0)
46 	, m_highlight(0)
47 	, m_highlight_enabled(true)
48 	, m_cyclic_scroll_enabled(false)
49 	, m_autocenter_cursor(false)
50 {
51 	auto fc = FormattedColor(m_base_color, {Format::Reverse});
52 	m_highlight_prefix << fc;
53 	m_highlight_suffix << FormattedColor::End<>(fc);
54 	m_items = &m_all_items;
55 }
56 
57 template <typename ItemT>
Menu(const Menu & rhs)58 Menu<ItemT>::Menu(const Menu &rhs)
59 	: Window(rhs)
60 	, m_item_displayer(rhs.m_item_displayer)
61 	, m_filter_predicate(rhs.m_filter_predicate)
62 	, m_beginning(rhs.m_beginning)
63 	, m_highlight(rhs.m_highlight)
64 	, m_highlight_enabled(rhs.m_highlight_enabled)
65 	, m_cyclic_scroll_enabled(rhs.m_cyclic_scroll_enabled)
66 	, m_autocenter_cursor(rhs.m_autocenter_cursor)
67 	, m_drawn_position(rhs.m_drawn_position)
68 	, m_highlight_prefix(rhs.m_highlight_prefix)
69 	, m_highlight_suffix(rhs.m_highlight_suffix)
70 	, m_selected_prefix(rhs.m_selected_prefix)
71 	, m_selected_suffix(rhs.m_selected_suffix)
72 {
73 	// TODO: move filtered items
74 	m_all_items.reserve(rhs.m_all_items.size());
75 	for (const auto &item : rhs.m_all_items)
76 		m_all_items.push_back(item.copy());
77 	m_items = &m_all_items;
78 }
79 
80 template <typename ItemT>
Menu(Menu && rhs)81 Menu<ItemT>::Menu(Menu &&rhs)
82 	: Window(rhs)
83 	, m_item_displayer(std::move(rhs.m_item_displayer))
84 	, m_filter_predicate(std::move(rhs.m_filter_predicate))
85 	, m_all_items(std::move(rhs.m_all_items))
86 	, m_filtered_items(std::move(rhs.m_filtered_items))
87 	, m_beginning(rhs.m_beginning)
88 	, m_highlight(rhs.m_highlight)
89 	, m_highlight_enabled(rhs.m_highlight_enabled)
90 	, m_cyclic_scroll_enabled(rhs.m_cyclic_scroll_enabled)
91 	, m_autocenter_cursor(rhs.m_autocenter_cursor)
92 	, m_drawn_position(rhs.m_drawn_position)
93 	, m_highlight_prefix(std::move(rhs.m_highlight_prefix))
94 	, m_highlight_suffix(std::move(rhs.m_highlight_suffix))
95 	, m_selected_prefix(std::move(rhs.m_selected_prefix))
96 	, m_selected_suffix(std::move(rhs.m_selected_suffix))
97 {
98 	if (rhs.m_items == &rhs.m_all_items)
99 		m_items = &m_all_items;
100 	else
101 		m_items = &m_filtered_items;
102 }
103 
104 template <typename ItemT>
105 Menu<ItemT> &Menu<ItemT>::operator=(Menu rhs)
106 {
107 	std::swap(static_cast<Window &>(*this), static_cast<Window &>(rhs));
108 	std::swap(m_item_displayer, rhs.m_item_displayer);
109 	std::swap(m_filter_predicate, rhs.m_filter_predicate);
110 	std::swap(m_all_items, rhs.m_all_items);
111 	std::swap(m_filtered_items, rhs.m_filtered_items);
112 	std::swap(m_beginning, rhs.m_beginning);
113 	std::swap(m_highlight, rhs.m_highlight);
114 	std::swap(m_highlight_enabled, rhs.m_highlight_enabled);
115 	std::swap(m_cyclic_scroll_enabled, rhs.m_cyclic_scroll_enabled);
116 	std::swap(m_autocenter_cursor, rhs.m_autocenter_cursor);
117 	std::swap(m_drawn_position, rhs.m_drawn_position);
118 	std::swap(m_highlight_prefix, rhs.m_highlight_prefix);
119 	std::swap(m_highlight_suffix, rhs.m_highlight_suffix);
120 	std::swap(m_selected_prefix, rhs.m_selected_prefix);
121 	std::swap(m_selected_suffix, rhs.m_selected_suffix);
122 	if (rhs.m_items == &rhs.m_all_items)
123 		m_items = &m_all_items;
124 	else
125 		m_items = &m_filtered_items;
126 	return *this;
127 }
128 
129 template <typename ItemT> template <typename ItemDisplayerT>
setItemDisplayer(ItemDisplayerT && displayer)130 void Menu<ItemT>::setItemDisplayer(ItemDisplayerT &&displayer)
131 {
132 	m_item_displayer = std::forward<ItemDisplayerT>(displayer);
133 }
134 
135 template <typename ItemT>
resizeList(size_t new_size)136 void Menu<ItemT>::resizeList(size_t new_size)
137 {
138 	m_all_items.resize(new_size);
139 }
140 
141 template <typename ItemT>
addItem(ItemT item,Properties::Type properties)142 void Menu<ItemT>::addItem(ItemT item, Properties::Type properties)
143 {
144 	m_all_items.push_back(Item(std::move(item), properties));
145 }
146 
147 template <typename ItemT>
addSeparator()148 void Menu<ItemT>::addSeparator()
149 {
150 	m_all_items.push_back(Item::mkSeparator());
151 }
152 
153 template <typename ItemT>
insertItem(size_t pos,ItemT item,Properties::Type properties)154 void Menu<ItemT>::insertItem(size_t pos, ItemT item, Properties::Type properties)
155 {
156 	m_all_items.insert(m_all_items.begin()+pos, Item(std::move(item), properties));
157 }
158 
159 template <typename ItemT>
insertSeparator(size_t pos)160 void Menu<ItemT>::insertSeparator(size_t pos)
161 {
162 	m_all_items.insert(m_all_items.begin()+pos, Item::mkSeparator());
163 }
164 
165 template <typename ItemT>
Goto(size_t y)166 bool Menu<ItemT>::Goto(size_t y)
167 {
168 	if (!isHighlightable(m_beginning+y))
169 		return false;
170 	m_highlight = m_beginning+y;
171 	return true;
172 }
173 
174 template <typename ItemT>
refresh()175 void Menu<ItemT>::refresh()
176 {
177 	if (m_items->empty())
178 	{
179 		Window::clear();
180 		Window::refresh();
181 		return;
182 	}
183 
184 	size_t max_beginning = 0;
185 	if (m_items->size() > m_height)
186 		max_beginning = m_items->size() - m_height;
187 	m_beginning = std::min(m_beginning, max_beginning);
188 
189 	// if highlighted position is off the screen, make it visible
190 	m_highlight = std::min(m_highlight, m_beginning+m_height-1);
191 	// if highlighted position is invalid, correct it
192 	m_highlight = std::min(m_highlight, m_items->size()-1);
193 
194 	if (!isHighlightable(m_highlight))
195 	{
196 		scroll(Scroll::Up);
197 		if (!isHighlightable(m_highlight))
198 			scroll(Scroll::Down);
199 	}
200 
201 	size_t line = 0;
202 	const size_t end_ = m_beginning+m_height;
203 	m_drawn_position = m_beginning;
204 	for (; m_drawn_position < end_; ++m_drawn_position, ++line)
205 	{
206 		goToXY(0, line);
207 		if (m_drawn_position >= m_items->size())
208 		{
209 			for (; line < m_height; ++line)
210 				mvwhline(m_window, line, 0, NC::Key::Space, m_width);
211 			break;
212 		}
213 		if ((*m_items)[m_drawn_position].isSeparator())
214 		{
215 			mvwhline(m_window, line, 0, 0, m_width);
216 			continue;
217 		}
218 		if (m_highlight_enabled && m_drawn_position == m_highlight)
219 			*this << m_highlight_prefix;
220 		if ((*m_items)[m_drawn_position].isSelected())
221 			*this << m_selected_prefix;
222 		*this << NC::TermManip::ClearToEOL;
223 		if (m_item_displayer)
224 			m_item_displayer(*this);
225 		if ((*m_items)[m_drawn_position].isSelected())
226 			*this << m_selected_suffix;
227 		if (m_highlight_enabled && m_drawn_position == m_highlight)
228 			*this << m_highlight_suffix;
229 	}
230 	Window::refresh();
231 }
232 
233 template <typename ItemT>
scroll(Scroll where)234 void Menu<ItemT>::scroll(Scroll where)
235 {
236 	if (m_items->empty())
237 		return;
238 	size_t max_highlight = m_items->size()-1;
239 	size_t max_beginning = m_items->size() < m_height ? 0 : m_items->size()-m_height;
240 	size_t max_visible_highlight = m_beginning+m_height-1;
241 	switch (where)
242 	{
243 		case Scroll::Up:
244 		{
245 			if (m_highlight <= m_beginning && m_highlight > 0)
246 				--m_beginning;
247 			if (m_highlight == 0)
248 			{
249 				if (m_cyclic_scroll_enabled)
250 					return scroll(Scroll::End);
251 				break;
252 			}
253 			else
254 				--m_highlight;
255 			if (!isHighlightable(m_highlight))
256 				scroll(m_highlight == 0 && !m_cyclic_scroll_enabled ? Scroll::Down : Scroll::Up);
257 			break;
258 		}
259 		case Scroll::Down:
260 		{
261 			if (m_highlight >= max_visible_highlight && m_highlight < max_highlight)
262 				++m_beginning;
263 			if (m_highlight == max_highlight)
264 			{
265 				if (m_cyclic_scroll_enabled)
266 					return scroll(Scroll::Home);
267 				break;
268 			}
269 			else
270 				++m_highlight;
271 			if (!isHighlightable(m_highlight))
272 				scroll(m_highlight == max_highlight && !m_cyclic_scroll_enabled ? Scroll::Up : Scroll::Down);
273 			break;
274 		}
275 		case Scroll::PageUp:
276 		{
277 			if (m_cyclic_scroll_enabled && m_highlight == 0)
278 				return scroll(Scroll::End);
279 			if (m_highlight < m_height)
280 				m_highlight = 0;
281 			else
282 				m_highlight -= m_height;
283 			if (m_beginning < m_height)
284 				m_beginning = 0;
285 			else
286 				m_beginning -= m_height;
287 			if (!isHighlightable(m_highlight))
288 				scroll(m_highlight == 0 && !m_cyclic_scroll_enabled ? Scroll::Down : Scroll::Up);
289 			break;
290 		}
291 		case Scroll::PageDown:
292 		{
293 			if (m_cyclic_scroll_enabled && m_highlight == max_highlight)
294 				return scroll(Scroll::Home);
295 			m_highlight += m_height;
296 			m_beginning += m_height;
297 			m_beginning = std::min(m_beginning, max_beginning);
298 			m_highlight = std::min(m_highlight, max_highlight);
299 			if (!isHighlightable(m_highlight))
300 				scroll(m_highlight == max_highlight && !m_cyclic_scroll_enabled ? Scroll::Up : Scroll::Down);
301 			break;
302 		}
303 		case Scroll::Home:
304 		{
305 			m_highlight = 0;
306 			m_beginning = 0;
307 			if (!isHighlightable(m_highlight))
308 				scroll(Scroll::Down);
309 			break;
310 		}
311 		case Scroll::End:
312 		{
313 			m_highlight = max_highlight;
314 			m_beginning = max_beginning;
315 			if (!isHighlightable(m_highlight))
316 				scroll(Scroll::Up);
317 			break;
318 		}
319 	}
320 	if (m_autocenter_cursor)
321 		highlight(m_highlight);
322 }
323 
324 template <typename ItemT>
reset()325 void Menu<ItemT>::reset()
326 {
327 	m_highlight = 0;
328 	m_beginning = 0;
329 }
330 
331 template <typename ItemT>
clear()332 void Menu<ItemT>::clear()
333 {
334 	// Don't clear filter related stuff here.
335 	m_all_items.clear();
336 	m_filtered_items.clear();
337 }
338 
339 template <typename ItemT>
highlight(size_t pos)340 void Menu<ItemT>::highlight(size_t pos)
341 {
342 	assert(pos < m_items->size());
343 	m_highlight = pos;
344 	size_t half_height = m_height/2;
345 	if (pos < half_height)
346 		m_beginning = 0;
347 	else
348 		m_beginning = pos-half_height;
349 }
350 
351 template <typename ItemT>
choice()352 size_t Menu<ItemT>::choice() const
353 {
354 	assert(!empty());
355 	return m_highlight;
356 }
357 
358 template <typename ItemT> template <typename PredicateT>
applyFilter(PredicateT && pred)359 void Menu<ItemT>::applyFilter(PredicateT &&pred)
360 {
361 	m_filter_predicate = std::forward<PredicateT>(pred);
362 	m_filtered_items.clear();
363 
364 	for (const auto &item : m_all_items)
365 		if (m_filter_predicate(item))
366 			m_filtered_items.push_back(item);
367 
368 	m_items = &m_filtered_items;
369 }
370 
371 template <typename ItemT>
reapplyFilter()372 void Menu<ItemT>::reapplyFilter()
373 {
374 	applyFilter(m_filter_predicate);
375 }
376 
377 template <typename ItemT> template <typename TargetT>
filterPredicate()378 const TargetT *Menu<ItemT>::filterPredicate() const
379 {
380 	return m_filter_predicate.template target<TargetT>();
381 }
382 
383 template <typename ItemT>
clearFilter()384 void Menu<ItemT>::clearFilter()
385 {
386 	m_filter_predicate = nullptr;
387 	m_filtered_items.clear();
388 	m_items = &m_all_items;
389 }
390 
391 }
392 
393 #endif // NCMPCPP_MENU_IMPL_H
394