ListWindow: move cursor management code to class ListCursor
authorMax Kellermann <max@musicpd.org>
Wed, 10 Apr 2019 07:44:07 +0000 (09:44 +0200)
committerMax Kellermann <max@musicpd.org>
Wed, 10 Apr 2019 07:44:07 +0000 (09:44 +0200)
meson.build
src/ListCursor.cxx [new file with mode: 0644]
src/ListCursor.hxx [new file with mode: 0644]
src/ListWindow.cxx
src/ListWindow.hxx

index 594445d..c6f6025 100644 (file)
@@ -334,6 +334,7 @@ ncmpc = executable('ncmpc',
   'src/FileListPage.cxx',
   'src/FileBrowserPage.cxx',
   'src/ProxyPage.cxx',
+  'src/ListCursor.cxx',
   'src/ListWindow.cxx',
   'src/TextListRenderer.cxx',
   'src/save_playlist.cxx',
diff --git a/src/ListCursor.cxx b/src/ListCursor.cxx
new file mode 100644 (file)
index 0000000..22a3a53
--- /dev/null
@@ -0,0 +1,313 @@
+/* ncmpc (Ncurses MPD Client)
+ * (c) 2004-2019 The Music Player Daemon Project
+ * Project homepage: http://musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "ListCursor.hxx"
+#include "Options.hxx"
+
+void
+ListCursor::Reset() noexcept
+{
+       selected = 0;
+       range_selection = false;
+       range_base = 0;
+       start = 0;
+}
+
+unsigned
+ListCursor::ValidateIndex(unsigned i) const noexcept
+{
+       if (length == 0)
+               return 0;
+       else if (i >= length)
+               return length - 1;
+       else
+               return i;
+}
+
+void
+ListCursor::CheckSelected() noexcept
+{
+       selected = ValidateIndex(selected);
+
+       if (range_selection)
+               range_base = ValidateIndex(range_base);
+}
+
+void
+ListCursor::Resize(Size _size) noexcept
+{
+       size = _size;
+       CheckOrigin();
+}
+
+void
+ListCursor::SetLength(unsigned _length) noexcept
+{
+       if (_length == length)
+               return;
+
+       length = _length;
+
+       CheckSelected();
+       CheckOrigin();
+}
+
+void
+ListCursor::Center(unsigned n) noexcept
+{
+       if (n > GetHeight() / 2)
+               start = n - GetHeight() / 2;
+       else
+               start = 0;
+
+       if (start + GetHeight() > length) {
+               if (GetHeight() < length)
+                       start = length - GetHeight();
+               else
+                       start = 0;
+       }
+}
+
+void
+ListCursor::ScrollTo(unsigned n) noexcept
+{
+       int new_start = start;
+
+       if ((unsigned) options.scroll_offset * 2 >= GetHeight())
+               // Center if the offset is more than half the screen
+               new_start = n - GetHeight() / 2;
+       else {
+               if (n < start + options.scroll_offset)
+                       new_start = n - options.scroll_offset;
+
+               if (n >= start + GetHeight() - options.scroll_offset)
+                       new_start = n - GetHeight() + 1 + options.scroll_offset;
+       }
+
+       if (new_start + GetHeight() > length)
+               new_start = length - GetHeight();
+
+       if (new_start < 0 || length == 0)
+               new_start = 0;
+
+       start = new_start;
+}
+
+void
+ListCursor::SetCursor(unsigned i) noexcept
+{
+       range_selection = false;
+       selected = i;
+
+       CheckSelected();
+       CheckOrigin();
+}
+
+void
+ListCursor::MoveCursor(unsigned n) noexcept
+{
+       selected = n;
+
+       CheckSelected();
+       CheckOrigin();
+}
+
+void
+ListCursor::FetchCursor() noexcept
+{
+       if (start > 0 &&
+           selected < start + options.scroll_offset)
+               MoveCursor(start + options.scroll_offset);
+       else if (start + GetHeight() < length &&
+                selected > start + GetHeight() - 1 - options.scroll_offset)
+               MoveCursor(start + GetHeight() - 1 - options.scroll_offset);
+}
+
+ListWindowRange
+ListCursor::GetRange() const noexcept
+{
+       if (length == 0) {
+               /* empty list - no selection */
+               return {0, 0};
+       } else if (range_selection) {
+               /* a range selection */
+               if (range_base < selected) {
+                       return {range_base, selected + 1};
+               } else {
+                       return {selected, range_base + 1};
+               }
+       } else {
+               /* no range, just the cursor */
+               return {selected, selected + 1};
+       }
+}
+
+void
+ListCursor::MoveCursorNext() noexcept
+{
+       if (selected + 1 < length)
+               MoveCursor(selected + 1);
+       else if (options.list_wrap)
+               MoveCursor(0);
+}
+
+void
+ListCursor::MoveCursorPrevious() noexcept
+{
+       if (selected > 0)
+               MoveCursor(selected - 1);
+       else if (options.list_wrap)
+               MoveCursor(length - 1);
+}
+
+void
+ListCursor::MoveCursorTop() noexcept
+{
+       if (start == 0)
+               MoveCursor(start);
+       else
+               if ((unsigned) options.scroll_offset * 2 >= GetHeight())
+                       MoveCursor(start + GetHeight() / 2);
+               else
+                       MoveCursor(start + options.scroll_offset);
+}
+
+void
+ListCursor::MoveCursorMiddle() noexcept
+{
+       if (length >= GetHeight())
+               MoveCursor(start + GetHeight() / 2);
+       else
+               MoveCursor(length / 2);
+}
+
+void
+ListCursor::MoveCursorBottom() noexcept
+{
+       if (length >= GetHeight())
+               if ((unsigned) options.scroll_offset * 2 >= GetHeight())
+                       MoveCursor(start + GetHeight() / 2);
+               else
+                       if (start + GetHeight() == length)
+                               MoveCursor(length - 1);
+                       else
+                               MoveCursor(start + GetHeight() - 1 - options.scroll_offset);
+       else
+               MoveCursor(length - 1);
+}
+
+void
+ListCursor::MoveCursorFirst() noexcept
+{
+       MoveCursor(0);
+}
+
+void
+ListCursor::MoveCursorLast() noexcept
+{
+       if (length > 0)
+               MoveCursor(length - 1);
+       else
+               MoveCursor(0);
+}
+
+void
+ListCursor::MoveCursorNextPage() noexcept
+{
+       if (GetHeight() < 2)
+               return;
+       if (selected + GetHeight() < length)
+               MoveCursor(selected + GetHeight() - 1);
+       else
+               MoveCursorLast();
+}
+
+void
+ListCursor::MoveCursorPreviousPage() noexcept
+{
+       if (GetHeight() < 2)
+               return;
+       if (selected > GetHeight() - 1)
+               MoveCursor(selected - GetHeight() + 1);
+       else
+               MoveCursorFirst();
+}
+
+void
+ListCursor::ScrollUp(unsigned n) noexcept
+{
+       if (start > 0) {
+               if (n > start)
+                       start = 0;
+               else
+                       start -= n;
+
+               FetchCursor();
+       }
+}
+
+void
+ListCursor::ScrollDown(unsigned n) noexcept
+{
+       if (start + GetHeight() < length) {
+               if (start + GetHeight() + n > length - 1)
+                       start = length - GetHeight();
+               else
+                       start += n;
+
+               FetchCursor();
+       }
+}
+
+void
+ListCursor::ScrollNextPage() noexcept
+{
+       start += GetHeight();
+       if (start + GetHeight() > length)
+               start = length > GetHeight()
+                       ? GetLength() - GetHeight()
+                       : 0;
+}
+
+void
+ListCursor::ScrollPreviousPage() noexcept
+{
+       start = start > GetHeight()
+               ? start - GetHeight()
+               : 0;
+}
+
+void
+ListCursor::ScrollNextHalfPage() noexcept
+{
+       start += (GetHeight() - 1) / 2;
+       if (start + GetHeight() > length) {
+               start = length > GetHeight()
+                       ? length - GetHeight()
+                       : 0;
+       }
+}
+
+void
+ListCursor::ScrollPreviousHalfPage() noexcept
+{
+       start = start > (GetHeight() - 1) / 2
+               ? start - (GetHeight() - 1) / 2
+               : 0;
+}
diff --git a/src/ListCursor.hxx b/src/ListCursor.hxx
new file mode 100644 (file)
index 0000000..9246456
--- /dev/null
@@ -0,0 +1,273 @@
+/* ncmpc (Ncurses MPD Client)
+ * (c) 2004-2019 The Music Player Daemon Project
+ * Project homepage: http://musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef LIST_CURSOR_HXX
+#define LIST_CURSOR_HXX
+
+#include "Size.hxx"
+#include "util/Compiler.h"
+
+/**
+ * The bounds of a range selection, see list_window_get_range().
+ */
+struct ListWindowRange {
+       /**
+        * The index of the first selected item.
+        */
+       unsigned start_index;
+
+       /**
+        * The index after the last selected item.  The selection is
+        * empty when this is the same as "start".
+        */
+       unsigned end_index;
+
+       constexpr bool empty() const noexcept {
+               return start_index >= end_index;
+       }
+
+       constexpr bool Contains(unsigned i) const noexcept {
+               return i >= start_index && i < end_index;
+       }
+
+       struct const_iterator {
+               unsigned value;
+
+               const_iterator &operator++() noexcept {
+                       ++value;
+                       return *this;
+               }
+
+               constexpr bool operator==(const const_iterator &other) const noexcept {
+                       return value == other.value;
+               }
+
+               constexpr bool operator!=(const const_iterator &other) const noexcept {
+                       return !(*this == other);
+               }
+
+               const unsigned &operator *() const noexcept {
+                       return value;
+               }
+       };
+
+       constexpr const_iterator begin() const noexcept {
+               return {start_index};
+       }
+
+       constexpr const_iterator end() const noexcept {
+               return {end_index};
+       }
+};
+
+class ListCursor {
+       Size size;
+
+       /**
+        * Number of items in this list.
+        */
+       unsigned length = 0;
+
+       unsigned start = 0;
+       unsigned selected = 0;
+
+       /**
+        * Represents the base item.
+        */
+       unsigned range_base = 0;
+
+       /**
+        * Range selection activated?
+        */
+       bool range_selection = false;
+
+       bool hide_cursor = false;
+
+public:
+       explicit constexpr ListCursor(Size _size) noexcept
+               :size(_size) {}
+
+       constexpr const Size &GetSize() const noexcept {
+               return size;
+       }
+
+       constexpr unsigned GetHeight() const noexcept {
+               return size.height;
+       }
+
+       constexpr unsigned GetOrigin() const noexcept {
+               return start;
+       }
+
+       constexpr bool IsVisible(unsigned i) const noexcept {
+               return i >= GetOrigin() && i < GetOrigin() + GetHeight();
+       }
+
+       void SetOrigin(unsigned new_orign) noexcept {
+               start = new_orign;
+       }
+
+       void DisableCursor() {
+               hide_cursor = true;
+       }
+
+       void EnableCursor() {
+               hide_cursor = false;
+       }
+
+       constexpr bool HasCursor() const noexcept {
+               return !hide_cursor;
+       }
+
+       constexpr bool HasRangeSelection() const noexcept {
+               return range_selection;
+       }
+
+       /**
+        * Is the cursor currently pointing to a single valid item?
+        */
+       constexpr bool IsSingleCursor() const noexcept {
+               return !HasRangeSelection() && selected < length;
+       }
+
+       constexpr unsigned GetCursorIndex() const noexcept {
+               return selected;
+       }
+
+       void SelectionMovedUp() noexcept {
+               selected--;
+               range_base--;
+
+               EnsureSelectionVisible();
+       }
+
+       void SelectionMovedDown() noexcept {
+               selected++;
+               range_base++;
+
+               EnsureSelectionVisible();
+       }
+
+       void EnsureSelectionVisible() noexcept {
+               if (range_selection)
+                       ScrollTo(range_base);
+               ScrollTo(selected);
+       }
+
+       /** reset a list window (selected=0, start=0) */
+       void Reset() noexcept;
+
+       void Resize(Size _size) noexcept;
+
+       void SetLength(unsigned length) noexcept;
+
+       constexpr unsigned GetLength() const noexcept {
+               return length;
+       }
+
+       /**
+        * Centers the visible range around item n on the list.
+        */
+       void Center(unsigned n) noexcept;
+
+       /**
+        * Scrolls the view to item n, as if the cursor would have been moved
+        * to the position.
+        */
+       void ScrollTo(unsigned n) noexcept;
+
+       /**
+        * Sets the position of the cursor.  Disables range selection.
+        */
+       void SetCursor(unsigned i) noexcept;
+
+       void SetCursorFromOrigin(unsigned i) noexcept {
+               SetCursor(GetOrigin() + i);
+       }
+
+       void EnableRangeSelection() noexcept {
+               range_base = selected;
+               range_selection = true;
+       }
+
+       void DisableRangeSelection() noexcept {
+               SetCursor(GetCursorIndex());
+       }
+
+       /**
+        * Moves the cursor.  Modifies the range if range selection is
+        * enabled.
+        */
+       void MoveCursor(unsigned n) noexcept;
+
+       void MoveCursorNext() noexcept;
+       void MoveCursorPrevious() noexcept;
+       void MoveCursorTop() noexcept;
+       void MoveCursorMiddle() noexcept;
+       void MoveCursorBottom() noexcept;
+       void MoveCursorFirst() noexcept;
+       void MoveCursorLast() noexcept;
+       void MoveCursorNextPage() noexcept;
+       void MoveCursorPreviousPage() noexcept;
+
+       void ScrollUp(unsigned n) noexcept;
+       void ScrollDown(unsigned n) noexcept;
+
+       void ScrollNextPage() noexcept;
+       void ScrollPreviousPage() noexcept;
+
+       void ScrollNextHalfPage() noexcept;
+       void ScrollPreviousHalfPage() noexcept;
+
+       void ScrollToBottom() noexcept {
+               start = length > GetHeight()
+                       ? GetLength() - GetHeight()
+                       : 0;
+       }
+
+       /**
+        * Ensures that the cursor is visible on the screen, i.e. it is not
+        * outside the current scrolling range.
+        */
+       void FetchCursor() noexcept;
+
+       /**
+        * Determines the lower and upper bound of the range selection.  If
+        * range selection is disabled, it returns the cursor position (range
+        * length is 1).
+        */
+       gcc_pure
+       ListWindowRange GetRange() const noexcept;
+
+private:
+       gcc_pure
+       unsigned ValidateIndex(unsigned i) const noexcept;
+
+       void CheckSelected() noexcept;
+
+       /**
+        * Scroll after the cursor was moved, the list was changed or
+        * the window was resized.
+        */
+       void CheckOrigin() noexcept {
+               ScrollTo(selected);
+       }
+};
+
+#endif
index ae22d1d..203a087 100644 (file)
@@ -1,5 +1,5 @@
 /* ncmpc (Ncurses MPD Client)
- * (c) 2004-2018 The Music Player Daemon Project
+ * (c) 2004-2019 The Music Player Daemon Project
  * Project homepage: http://musicpd.org
  *
  * This program is free software; you can redistribute it and/or modify
 #include <string.h>
 
 void
-ListWindow::Reset() noexcept
-{
-       selected = 0;
-       range_selection = false;
-       range_base = 0;
-       start = 0;
-}
-
-unsigned
-ListWindow::ValidateIndex(unsigned i) const noexcept
-{
-       if (length == 0)
-               return 0;
-       else if (i >= length)
-               return length - 1;
-       else
-               return i;
-}
-
-void
-ListWindow::CheckSelected() noexcept
-{
-       selected = ValidateIndex(selected);
-
-       if (range_selection)
-               range_base = ValidateIndex(range_base);
-}
-
-void
-ListWindow::Resize(Size _size) noexcept
-{
-       size = _size;
-       CheckOrigin();
-}
-
-void
-ListWindow::SetLength(unsigned _length) noexcept
-{
-       if (_length == length)
-               return;
-
-       length = _length;
-
-       CheckSelected();
-       CheckOrigin();
-}
-
-void
-ListWindow::Center(unsigned n) noexcept
-{
-       if (n > size.height / 2)
-               start = n - size.height / 2;
-       else
-               start = 0;
-
-       if (start + size.height > length) {
-               if (size.height < length)
-                       start = length - size.height;
-               else
-                       start = 0;
-       }
-}
-
-void
-ListWindow::ScrollTo(unsigned n) noexcept
-{
-       int new_start = start;
-
-       if ((unsigned) options.scroll_offset * 2 >= size.height)
-               // Center if the offset is more than half the screen
-               new_start = n - size.height / 2;
-       else {
-               if (n < start + options.scroll_offset)
-                       new_start = n - options.scroll_offset;
-
-               if (n >= start + size.height - options.scroll_offset)
-                       new_start = n - size.height + 1 + options.scroll_offset;
-       }
-
-       if (new_start + size.height > length)
-               new_start = length - size.height;
-
-       if (new_start < 0 || length == 0)
-               new_start = 0;
-
-       start = new_start;
-}
-
-void
-ListWindow::SetCursor(unsigned i) noexcept
-{
-       range_selection = false;
-       selected = i;
-
-       CheckSelected();
-       CheckOrigin();
-}
-
-void
-ListWindow::MoveCursor(unsigned n) noexcept
-{
-       selected = n;
-
-       CheckSelected();
-       CheckOrigin();
-}
-
-void
-ListWindow::FetchCursor() noexcept
-{
-       if (start > 0 &&
-           selected < start + options.scroll_offset)
-               MoveCursor(start + options.scroll_offset);
-       else if (start + size.height < length &&
-                selected > start + size.height - 1 - options.scroll_offset)
-               MoveCursor(start + size.height - 1 - options.scroll_offset);
-}
-
-ListWindowRange
-ListWindow::GetRange() const noexcept
-{
-       if (length == 0) {
-               /* empty list - no selection */
-               return {0, 0};
-       } else if (range_selection) {
-               /* a range selection */
-               if (range_base < selected) {
-                       return {range_base, selected + 1};
-               } else {
-                       return {selected, range_base + 1};
-               }
-       } else {
-               /* no range, just the cursor */
-               return {selected, selected + 1};
-       }
-}
-
-void
-ListWindow::MoveCursorNext() noexcept
-{
-       if (selected + 1 < length)
-               MoveCursor(selected + 1);
-       else if (options.list_wrap)
-               MoveCursor(0);
-}
-
-void
-ListWindow::MoveCursorPrevious() noexcept
-{
-       if (selected > 0)
-               MoveCursor(selected - 1);
-       else if (options.list_wrap)
-               MoveCursor(length - 1);
-}
-
-void
-ListWindow::MoveCursorTop() noexcept
-{
-       if (start == 0)
-               MoveCursor(start);
-       else
-               if ((unsigned) options.scroll_offset * 2 >= size.height)
-                       MoveCursor(start + size.height / 2);
-               else
-                       MoveCursor(start + options.scroll_offset);
-}
-
-void
-ListWindow::MoveCursorMiddle() noexcept
-{
-       if (length >= size.height)
-               MoveCursor(start + size.height / 2);
-       else
-               MoveCursor(length / 2);
-}
-
-void
-ListWindow::MoveCursorBottom() noexcept
-{
-       if (length >= size.height)
-               if ((unsigned) options.scroll_offset * 2 >= size.height)
-                       MoveCursor(start + size.height / 2);
-               else
-                       if (start + size.height == length)
-                               MoveCursor(length - 1);
-                       else
-                               MoveCursor(start + size.height - 1 - options.scroll_offset);
-       else
-               MoveCursor(length - 1);
-}
-
-void
-ListWindow::MoveCursorFirst() noexcept
-{
-       MoveCursor(0);
-}
-
-void
-ListWindow::MoveCursorLast() noexcept
-{
-       if (length > 0)
-               MoveCursor(length - 1);
-       else
-               MoveCursor(0);
-}
-
-void
-ListWindow::MoveCursorNextPage() noexcept
-{
-       if (size.height < 2)
-               return;
-       if (selected + size.height < length)
-               MoveCursor(selected + size.height - 1);
-       else
-               MoveCursorLast();
-}
-
-void
-ListWindow::MoveCursorPreviousPage() noexcept
-{
-       if (size.height < 2)
-               return;
-       if (selected > size.height - 1)
-               MoveCursor(selected - size.height + 1);
-       else
-               MoveCursorFirst();
-}
-
-void
-ListWindow::ScrollUp(unsigned n) noexcept
-{
-       if (start > 0) {
-               if (n > start)
-                       start = 0;
-               else
-                       start -= n;
-
-               FetchCursor();
-       }
-}
-
-void
-ListWindow::ScrollDown(unsigned n) noexcept
-{
-       if (start + size.height < length) {
-               if (start + size.height + n > length - 1)
-                       start = length - size.height;
-               else
-                       start += n;
-
-               FetchCursor();
-       }
-}
-
-void
 ListWindow::Paint(const ListRenderer &renderer) const noexcept
 {
-       bool show_cursor = !hide_cursor &&
-               (!options.hardware_cursor || range_selection);
+       bool show_cursor = HasCursor() &&
+               (!options.hardware_cursor || HasRangeSelection());
        ListWindowRange range;
 
        if (show_cursor)
                range = GetRange();
 
-       for (unsigned i = 0; i < size.height; i++) {
+       for (unsigned i = 0; i < GetSize().height; i++) {
                wmove(w, i, 0);
 
-               if (start + i >= length) {
+               const unsigned j = GetOrigin() + i;
+               if (j >= GetLength()) {
                        wclrtobot(w);
                        break;
                }
 
                bool is_selected = show_cursor &&
-                       range.Contains(start + i);
+                       range.Contains(j);
 
-               renderer.PaintListItem(w, start + i, i, size.width,
-                                      is_selected);
+               renderer.PaintListItem(w, j, i, GetSize().width, is_selected);
        }
 
        row_color_end(w);
 
-       if (options.hardware_cursor && selected >= start &&
-           selected < start + size.height) {
+       if (options.hardware_cursor && IsVisible(GetCursorIndex())) {
                curs_set(1);
-               wmove(w, selected - start, 0);
+               wmove(w, GetCursorIndex() - GetOrigin(), 0);
        }
 }
 
@@ -330,7 +74,7 @@ ListWindow::Find(const ListText &text,
                 bool wrap,
                 bool bell_on_wrap) noexcept
 {
-       unsigned i = selected + 1;
+       unsigned i = GetCursorIndex() + 1;
 
        assert(str != nullptr);
 
@@ -339,7 +83,7 @@ ListWindow::Find(const ListText &text,
                return false;
 
        do {
-               while (i < length) {
+               while (i < GetLength()) {
                        char buffer[1024];
                        const char *label =
                                text.GetListItemText(buffer, sizeof(buffer),
@@ -350,7 +94,7 @@ ListWindow::Find(const ListText &text,
                                MoveCursor(i);
                                return true;
                        }
-                       if (wrap && i == selected)
+                       if (wrap && i == GetCursorIndex())
                                return false;
                        i++;
                }
@@ -373,11 +117,11 @@ ListWindow::ReverseFind(const ListText &text,
                        bool wrap,
                        bool bell_on_wrap) noexcept
 {
-       int i = selected - 1;
+       int i = GetCursorIndex() - 1;
 
        assert(str != nullptr);
 
-       if (length == 0)
+       if (GetLength() == 0)
                return false;
 
        MatchExpression m;
@@ -396,12 +140,12 @@ ListWindow::ReverseFind(const ListText &text,
                                MoveCursor(i);
                                return true;
                        }
-                       if (wrap && i == (int)selected)
+                       if (wrap && i == (int)GetCursorIndex())
                                return false;
                        i--;
                }
                if (wrap) {
-                       i = length - 1; /* last item */
+                       i = GetLength() - 1; /* last item */
                        if (bell_on_wrap) {
                                screen_bell();
                        }
@@ -420,7 +164,7 @@ ListWindow::Jump(const ListText &text, const char *str) noexcept
        if (!m.Compile(str, options.jump_prefix_only))
                return false;
 
-       for (unsigned i = 0; i < length; i++) {
+       for (unsigned i = 0; i < GetLength(); i++) {
                char buffer[1024];
                const char *label =
                        text.GetListItemText(buffer, sizeof(buffer),
@@ -469,16 +213,12 @@ ListWindow::HandleCommand(Command cmd) noexcept
                MoveCursorPreviousPage();
                break;
        case Command::LIST_RANGE_SELECT:
-               if(range_selection)
-               {
+               if (HasRangeSelection()) {
                        screen_status_message(_("Range selection disabled"));
-                       SetCursor(selected);
-               }
-               else
-               {
+                       DisableRangeSelection();
+               } else {
                        screen_status_message(_("Range selection enabled"));
-                       range_base = selected;
-                       range_selection = true;
+                       EnableRangeSelection();
                }
                break;
        case Command::LIST_SCROLL_UP_LINE:
@@ -488,10 +228,10 @@ ListWindow::HandleCommand(Command cmd) noexcept
                ScrollDown(1);
                break;
        case Command::LIST_SCROLL_UP_HALF:
-               ScrollUp((size.height - 1) / 2);
+               ScrollUp((GetHeight() - 1) / 2);
                break;
        case Command::LIST_SCROLL_DOWN_HALF:
-               ScrollDown((size.height - 1) / 2);
+               ScrollDown((GetHeight() - 1) / 2);
                break;
        default:
                return false;
@@ -506,59 +246,36 @@ ListWindow::HandleScrollCommand(Command cmd) noexcept
        switch (cmd) {
        case Command::LIST_SCROLL_UP_LINE:
        case Command::LIST_PREVIOUS:
-               if (start > 0)
-                       start--;
+               ScrollUp(1);
                break;
 
        case Command::LIST_SCROLL_DOWN_LINE:
        case Command::LIST_NEXT:
-               if (start + size.height < length)
-                       start++;
+               ScrollDown(1);
                break;
 
        case Command::LIST_FIRST:
-               start = 0;
+               SetOrigin(0);
                break;
 
        case Command::LIST_LAST:
-               if (length > size.height)
-                       start = length - size.height;
-               else
-                       start = 0;
+               ScrollToBottom();
                break;
 
        case Command::LIST_NEXT_PAGE:
-               start += size.height;
-               if (start + size.height > length) {
-                       if (length > size.height)
-                               start = length - size.height;
-                       else
-                               start = 0;
-               }
+               ScrollNextPage();
                break;
 
        case Command::LIST_PREVIOUS_PAGE:
-               if (start > size.height)
-                       start -= size.height;
-               else
-                       start = 0;
+               ScrollPreviousPage();
                break;
 
        case Command::LIST_SCROLL_UP_HALF:
-               if (start > (size.height - 1) / 2)
-                       start -= (size.height - 1) / 2;
-               else
-                       start = 0;
+               ScrollPreviousHalfPage();
                break;
 
        case Command::LIST_SCROLL_DOWN_HALF:
-               start += (size.height - 1) / 2;
-               if (start + size.height > length) {
-                       if (length > size.height)
-                               start = length - size.height;
-                       else
-                               start = 0;
-               }
+               ScrollNextHalfPage();
                break;
 
        default:
@@ -582,7 +299,7 @@ ListWindow::HandleMouse(mmask_t bstate, int y) noexcept
        }
 
        /* if the even occurred below the list window move down */
-       if ((unsigned)y >= length) {
+       if ((unsigned)y >= GetLength()) {
                if (bstate & BUTTON3_CLICKED)
                        MoveCursorLast();
                else
index 0485913..2adc7d6 100644 (file)
@@ -1,5 +1,5 @@
 /* ncmpc (Ncurses MPD Client)
- * (c) 2004-2018 The Music Player Daemon Project
+ * (c) 2004-2019 The Music Player Daemon Project
  * Project homepage: http://musicpd.org
  *
  * This program is free software; you can redistribute it and/or modify
@@ -20,9 +20,8 @@
 #ifndef LIST_WINDOW_HXX
 #define LIST_WINDOW_HXX
 
+#include "ListCursor.hxx"
 #include "config.h"
-#include "Size.hxx"
-#include "util/Compiler.h"
 
 #include <curses.h>
 
@@ -30,157 +29,17 @@ enum class Command : unsigned;
 class ListText;
 class ListRenderer;
 
-/**
- * The bounds of a range selection, see list_window_get_range().
- */
-struct ListWindowRange {
-       /**
-        * The index of the first selected item.
-        */
-       unsigned start_index;
-
-       /**
-        * The index after the last selected item.  The selection is
-        * empty when this is the same as "start".
-        */
-       unsigned end_index;
-
-       constexpr bool empty() const noexcept {
-               return start_index >= end_index;
-       }
-
-       constexpr bool Contains(unsigned i) const noexcept {
-               return i >= start_index && i < end_index;
-       }
-
-       struct const_iterator {
-               unsigned value;
-
-               const_iterator &operator++() noexcept {
-                       ++value;
-                       return *this;
-               }
-
-               constexpr bool operator==(const const_iterator &other) const noexcept {
-                       return value == other.value;
-               }
-
-               constexpr bool operator!=(const const_iterator &other) const noexcept {
-                       return !(*this == other);
-               }
-
-               const unsigned &operator *() const noexcept {
-                       return value;
-               }
-       };
-
-       constexpr const_iterator begin() const noexcept {
-               return {start_index};
-       }
-
-       constexpr const_iterator end() const noexcept {
-               return {end_index};
-       }
-};
-
-class ListWindow {
+class ListWindow : public ListCursor {
        WINDOW *const w;
-       Size size;
-
-       /**
-        * Number of items in this list.
-        */
-       unsigned length = 0;
-
-       unsigned start = 0;
-       unsigned selected = 0;
-
-       /**
-        * Represents the base item.
-        */
-       unsigned range_base = 0;
-
-       /**
-        * Range selection activated?
-        */
-       bool range_selection = false;
-
-       bool hide_cursor = false;
 
 public:
        ListWindow(WINDOW *_w, Size _size) noexcept
-               :w(_w), size(_size) {}
-
-       const Size &GetSize() const noexcept {
-               return size;
-       }
+               :ListCursor(_size), w(_w) {}
 
        void Refresh() const noexcept {
                wrefresh(w);
        }
 
-       unsigned GetOrigin() const noexcept {
-               return start;
-       }
-
-       void SetOrigin(unsigned new_orign) noexcept {
-               start = new_orign;
-       }
-
-       void DisableCursor() {
-               hide_cursor = true;
-       }
-
-       void EnableCursor() {
-               hide_cursor = false;
-       }
-
-       bool HasCursor() const noexcept {
-               return !hide_cursor;
-       }
-
-       bool HasRangeSelection() const noexcept {
-               return range_selection;
-       }
-
-       /**
-        * Is the cursor currently pointing to a single valid item?
-        */
-       bool IsSingleCursor() const noexcept {
-               return !HasRangeSelection() && selected < length;
-       }
-
-       unsigned GetCursorIndex() const noexcept {
-               return selected;
-       }
-
-       void SelectionMovedUp() noexcept {
-               selected--;
-               range_base--;
-
-               EnsureSelectionVisible();
-       }
-
-       void SelectionMovedDown() noexcept {
-               selected++;
-               range_base++;
-
-               EnsureSelectionVisible();
-       }
-
-       void EnsureSelectionVisible() noexcept {
-               if (range_selection)
-                       ScrollTo(range_base);
-               ScrollTo(selected);
-       }
-
-       /** reset a list window (selected=0, start=0) */
-       void Reset() noexcept;
-
-       void Resize(Size _size) noexcept;
-
-       void SetLength(unsigned length) noexcept;
-
        void Paint(const ListRenderer &renderer) const noexcept;
 
        /** perform basic list window commands (movement) */
@@ -201,59 +60,6 @@ public:
 #endif
 
        /**
-        * Centers the visible range around item n on the list.
-        */
-       void Center(unsigned n) noexcept;
-
-       /**
-        * Scrolls the view to item n, as if the cursor would have been moved
-        * to the position.
-        */
-       void ScrollTo(unsigned n) noexcept;
-
-       /**
-        * Sets the position of the cursor.  Disables range selection.
-        */
-       void SetCursor(unsigned i) noexcept;
-
-       void SetCursorFromOrigin(unsigned i) noexcept {
-               SetCursor(GetOrigin() + i);
-       }
-
-       /**
-        * Moves the cursor.  Modifies the range if range selection is
-        * enabled.
-        */
-       void MoveCursor(unsigned n) noexcept;
-
-       void MoveCursorNext() noexcept;
-       void MoveCursorPrevious() noexcept;
-       void MoveCursorTop() noexcept;
-       void MoveCursorMiddle() noexcept;
-       void MoveCursorBottom() noexcept;
-       void MoveCursorFirst() noexcept;
-       void MoveCursorLast() noexcept;
-       void MoveCursorNextPage() noexcept;
-       void MoveCursorPreviousPage() noexcept;
-
-       void ScrollUp(unsigned n) noexcept;
-       void ScrollDown(unsigned n) noexcept;
-
-       /**
-        * Ensures that the cursor is visible on the screen, i.e. it is not
-        * outside the current scrolling range.
-        */
-       void FetchCursor() noexcept;
-
-       /**
-        * Determines the lower and upper bound of the range selection.  If
-        * range selection is disabled, it returns the cursor position (range
-        * length is 1).
-        */
-       gcc_pure
-       ListWindowRange GetRange() const noexcept;
-
-       /**
         * Find a string in a list window.
         */
        bool Find(const ListText &text,
@@ -274,20 +80,6 @@ public:
         * characters in *str.
         */
        bool Jump(const ListText &text, const char *str) noexcept;
-
-private:
-       gcc_pure
-       unsigned ValidateIndex(unsigned i) const noexcept;
-
-       void CheckSelected() noexcept;
-
-       /**
-        * Scroll after the cursor was moved, the list was changed or
-        * the window was resized.
-        */
-       void CheckOrigin() noexcept {
-               ScrollTo(selected);
-       }
 };
 
 #endif