1 /* ncmpc (Ncurses MPD Client)
2  * (c) 2004-2020 The Music Player Daemon Project
3  * Project homepage: http://musicpd.org
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 along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19 
20 #include "ListCursor.hxx"
21 #include "Options.hxx"
22 
23 void
Reset()24 ListCursor::Reset() noexcept
25 {
26 	selected = 0;
27 	range_selection = false;
28 	range_base = 0;
29 	start = 0;
30 }
31 
32 unsigned
ValidateIndex(unsigned i) const33 ListCursor::ValidateIndex(unsigned i) const noexcept
34 {
35 	if (length == 0)
36 		return 0;
37 	else if (i >= length)
38 		return length - 1;
39 	else
40 		return i;
41 }
42 
43 void
CheckSelected()44 ListCursor::CheckSelected() noexcept
45 {
46 	selected = ValidateIndex(selected);
47 
48 	if (range_selection)
49 		range_base = ValidateIndex(range_base);
50 }
51 
52 void
SetHeight(unsigned _height)53 ListCursor::SetHeight(unsigned _height) noexcept
54 {
55 	height = _height;
56 	CheckOrigin();
57 }
58 
59 void
SetLength(unsigned _length)60 ListCursor::SetLength(unsigned _length) noexcept
61 {
62 	if (_length == length)
63 		return;
64 
65 	length = _length;
66 
67 	CheckSelected();
68 	CheckOrigin();
69 }
70 
71 void
Center(unsigned n)72 ListCursor::Center(unsigned n) noexcept
73 {
74 	if (n > GetHeight() / 2)
75 		start = n - GetHeight() / 2;
76 	else
77 		start = 0;
78 
79 	if (start + GetHeight() > length) {
80 		if (GetHeight() < length)
81 			start = length - GetHeight();
82 		else
83 			start = 0;
84 	}
85 }
86 
87 void
ScrollTo(unsigned n)88 ListCursor::ScrollTo(unsigned n) noexcept
89 {
90 	int new_start = start;
91 
92 	if (options.scroll_offset * 2 >= GetHeight())
93 		// Center if the offset is more than half the screen
94 		new_start = n - GetHeight() / 2;
95 	else {
96 		if (n < start + options.scroll_offset)
97 			new_start = n - options.scroll_offset;
98 
99 		if (n >= start + GetHeight() - options.scroll_offset)
100 			new_start = n - GetHeight() + 1 + options.scroll_offset;
101 	}
102 
103 	if (new_start + GetHeight() > length)
104 		new_start = length - GetHeight();
105 
106 	if (new_start < 0 || length == 0)
107 		new_start = 0;
108 
109 	start = new_start;
110 }
111 
112 void
SetCursor(unsigned i)113 ListCursor::SetCursor(unsigned i) noexcept
114 {
115 	range_selection = false;
116 	selected = i;
117 
118 	CheckSelected();
119 	CheckOrigin();
120 }
121 
122 void
MoveCursor(unsigned n)123 ListCursor::MoveCursor(unsigned n) noexcept
124 {
125 	selected = n;
126 
127 	CheckSelected();
128 	CheckOrigin();
129 }
130 
131 void
FetchCursor()132 ListCursor::FetchCursor() noexcept
133 {
134 	/* clamp the scroll-offset setting to slightly less than half
135 	   of the screen height */
136 	const unsigned scroll_offset = options.scroll_offset * 2 < GetHeight()
137 		? options.scroll_offset
138 		: std::max(GetHeight() / 2, 1U) - 1;
139 
140 	if (start > 0 &&
141 	    selected < start + scroll_offset)
142 		MoveCursor(start + scroll_offset);
143 	else if (start + GetHeight() < length &&
144 		 selected > start + GetHeight() - 1 - scroll_offset)
145 		MoveCursor(start + GetHeight() - 1 - scroll_offset);
146 }
147 
148 ListWindowRange
GetRange() const149 ListCursor::GetRange() const noexcept
150 {
151 	if (length == 0) {
152 		/* empty list - no selection */
153 		return {0, 0};
154 	} else if (range_selection) {
155 		/* a range selection */
156 		if (range_base < selected) {
157 			return {range_base, selected + 1};
158 		} else {
159 			return {selected, range_base + 1};
160 		}
161 	} else {
162 		/* no range, just the cursor */
163 		return {selected, selected + 1};
164 	}
165 }
166 
167 void
MoveCursorNext()168 ListCursor::MoveCursorNext() noexcept
169 {
170 	if (selected + 1 < length)
171 		MoveCursor(selected + 1);
172 	else if (options.list_wrap)
173 		MoveCursor(0);
174 }
175 
176 void
MoveCursorPrevious()177 ListCursor::MoveCursorPrevious() noexcept
178 {
179 	if (selected > 0)
180 		MoveCursor(selected - 1);
181 	else if (options.list_wrap)
182 		MoveCursor(length - 1);
183 }
184 
185 void
MoveCursorTop()186 ListCursor::MoveCursorTop() noexcept
187 {
188 	if (start == 0)
189 		MoveCursor(start);
190 	else
191 		if (options.scroll_offset * 2 >= GetHeight())
192 			MoveCursor(start + GetHeight() / 2);
193 		else
194 			MoveCursor(start + options.scroll_offset);
195 }
196 
197 void
MoveCursorMiddle()198 ListCursor::MoveCursorMiddle() noexcept
199 {
200 	if (length >= GetHeight())
201 		MoveCursor(start + GetHeight() / 2);
202 	else
203 		MoveCursor(length / 2);
204 }
205 
206 void
MoveCursorBottom()207 ListCursor::MoveCursorBottom() noexcept
208 {
209 	if (length >= GetHeight())
210 		if (options.scroll_offset * 2 >= GetHeight())
211 			MoveCursor(start + GetHeight() / 2);
212 		else
213 			if (start + GetHeight() == length)
214 				MoveCursor(length - 1);
215 			else
216 				MoveCursor(start + GetHeight() - 1 - options.scroll_offset);
217 	else
218 		MoveCursor(length - 1);
219 }
220 
221 void
MoveCursorFirst()222 ListCursor::MoveCursorFirst() noexcept
223 {
224 	MoveCursor(0);
225 }
226 
227 void
MoveCursorLast()228 ListCursor::MoveCursorLast() noexcept
229 {
230 	if (length > 0)
231 		MoveCursor(length - 1);
232 	else
233 		MoveCursor(0);
234 }
235 
236 void
MoveCursorNextPage()237 ListCursor::MoveCursorNextPage() noexcept
238 {
239 	if (GetHeight() < 2)
240 		return;
241 	if (selected + GetHeight() < length)
242 		MoveCursor(selected + GetHeight() - 1);
243 	else
244 		MoveCursorLast();
245 }
246 
247 void
MoveCursorPreviousPage()248 ListCursor::MoveCursorPreviousPage() noexcept
249 {
250 	if (GetHeight() < 2)
251 		return;
252 	if (selected > GetHeight() - 1)
253 		MoveCursor(selected - GetHeight() + 1);
254 	else
255 		MoveCursorFirst();
256 }
257 
258 void
ScrollUp(unsigned n)259 ListCursor::ScrollUp(unsigned n) noexcept
260 {
261 	if (start > 0) {
262 		if (n > start)
263 			start = 0;
264 		else
265 			start -= n;
266 
267 		FetchCursor();
268 	}
269 }
270 
271 void
ScrollDown(unsigned n)272 ListCursor::ScrollDown(unsigned n) noexcept
273 {
274 	if (start + GetHeight() < length) {
275 		if (start + GetHeight() + n > length - 1)
276 			start = length - GetHeight();
277 		else
278 			start += n;
279 
280 		FetchCursor();
281 	}
282 }
283 
284 void
ScrollNextPage()285 ListCursor::ScrollNextPage() noexcept
286 {
287 	start += GetHeight();
288 	if (start + GetHeight() > length)
289 		start = length > GetHeight()
290 			? GetLength() - GetHeight()
291 			: 0;
292 }
293 
294 void
ScrollPreviousPage()295 ListCursor::ScrollPreviousPage() noexcept
296 {
297 	start = start > GetHeight()
298 		? start - GetHeight()
299 		: 0;
300 }
301 
302 void
ScrollNextHalfPage()303 ListCursor::ScrollNextHalfPage() noexcept
304 {
305 	start += (GetHeight() - 1) / 2;
306 	if (start + GetHeight() > length) {
307 		start = length > GetHeight()
308 			? length - GetHeight()
309 			: 0;
310 	}
311 }
312 
313 void
ScrollPreviousHalfPage()314 ListCursor::ScrollPreviousHalfPage() noexcept
315 {
316 	start = start > (GetHeight() - 1) / 2
317 		? start - (GetHeight() - 1) / 2
318 		: 0;
319 }
320