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