136 lines
5.8 KiB
Python
136 lines
5.8 KiB
Python
"""Round 25: folder highlight while dragging a playlist.
|
|
|
|
Hovering a folder mid-drag lights it up so the user can see the drop would
|
|
land inside it (previously there was no indication at all). A folder in the
|
|
dragged row's own branch is never highlighted — the manager rejects that drop
|
|
as a cycle, so lighting it up would lie. The autoscroll tick's highlight
|
|
refresh is now kind-aware so a folder highlight survives edge auto-scrolling.
|
|
|
|
Synthetic QDropEvents report source() == None, so the internal-move branch of
|
|
dragMoveEvent can't be driven directly; _highlight_drop_target carries the
|
|
logic and is what gets tested.
|
|
"""
|
|
|
|
from PyQt6.QtCore import QPoint
|
|
|
|
from lintunes.models import Library, Track
|
|
from lintunes.library_manager import LibraryManager
|
|
from lintunes.gui.sidebar import PlaylistTree
|
|
|
|
|
|
def _tree(qapp, tmp_path, height=400):
|
|
library = Library(tracks={1: Track(track_id=1, name="One")})
|
|
manager = LibraryManager(library, tmp_path)
|
|
tree = PlaylistTree(manager)
|
|
tree.resize(200, height)
|
|
tree.show()
|
|
return manager, tree
|
|
|
|
|
|
def _pos_over(tree, item) -> QPoint:
|
|
return tree.visualItemRect(item).center()
|
|
|
|
|
|
class TestFolderHighlight:
|
|
def test_folder_highlighted_while_dragging_playlist(self, qapp, tmp_path):
|
|
manager, tree = _tree(qapp, tmp_path)
|
|
folder = manager.create_folder("a folder")
|
|
playlist = manager.create_playlist("a playlist")
|
|
folder_item = tree._find_item(folder.persistent_id)
|
|
tree.setCurrentItem(tree._find_item(playlist.persistent_id))
|
|
|
|
tree._highlight_drop_target(_pos_over(tree, folder_item), "folder")
|
|
assert tree._drop_target is folder_item
|
|
|
|
def test_leaving_the_folder_clears_the_highlight(self, qapp, tmp_path):
|
|
manager, tree = _tree(qapp, tmp_path)
|
|
folder = manager.create_folder("a folder")
|
|
playlist = manager.create_playlist("a playlist")
|
|
folder_item = tree._find_item(folder.persistent_id)
|
|
tree.setCurrentItem(tree._find_item(playlist.persistent_id))
|
|
|
|
tree._highlight_drop_target(_pos_over(tree, folder_item), "folder")
|
|
assert tree._drop_target is not None
|
|
# Hover empty space below the rows (drop there = top level).
|
|
tree._highlight_drop_target(
|
|
QPoint(10, tree.viewport().height() - 2), "folder")
|
|
assert tree._drop_target is None
|
|
|
|
def test_playlist_row_is_not_a_folder_target(self, qapp, tmp_path):
|
|
manager, tree = _tree(qapp, tmp_path)
|
|
playlist = manager.create_playlist("a playlist")
|
|
other = manager.create_playlist("another playlist")
|
|
tree.setCurrentItem(tree._find_item(other.persistent_id))
|
|
|
|
tree._highlight_drop_target(
|
|
_pos_over(tree, tree._find_item(playlist.persistent_id)), "folder")
|
|
assert tree._drop_target is None
|
|
|
|
def test_dragged_folder_cannot_drop_into_its_own_branch(
|
|
self, qapp, tmp_path):
|
|
manager, tree = _tree(qapp, tmp_path)
|
|
outer = manager.create_folder("a outer")
|
|
inner = manager.create_folder("b inner", outer.persistent_id)
|
|
other = manager.create_folder("c other")
|
|
outer_item = tree._find_item(outer.persistent_id)
|
|
inner_item = tree._find_item(inner.persistent_id)
|
|
other_item = tree._find_item(other.persistent_id)
|
|
outer_item.setExpanded(True)
|
|
tree.setCurrentItem(outer_item) # dragging "outer"
|
|
|
|
# Onto itself: no highlight (manager would no-op the drop).
|
|
tree._highlight_drop_target(_pos_over(tree, outer_item), "folder")
|
|
assert tree._drop_target is None
|
|
# Into its own subfolder: no highlight (manager rejects the cycle).
|
|
tree._highlight_drop_target(_pos_over(tree, inner_item), "folder")
|
|
assert tree._drop_target is None
|
|
# Into an unrelated folder: highlighted.
|
|
tree._highlight_drop_target(_pos_over(tree, other_item), "folder")
|
|
assert tree._drop_target is other_item
|
|
|
|
def test_track_drag_still_highlights_playlists(self, qapp, tmp_path):
|
|
manager, tree = _tree(qapp, tmp_path)
|
|
folder = manager.create_folder("a folder")
|
|
playlist = manager.create_playlist("a playlist")
|
|
folder_item = tree._find_item(folder.persistent_id)
|
|
playlist_item = tree._find_item(playlist.persistent_id)
|
|
|
|
tree._highlight_drop_target(_pos_over(tree, playlist_item), "playlist")
|
|
assert tree._drop_target is playlist_item
|
|
# Folders are not track-drop targets.
|
|
tree._highlight_drop_target(_pos_over(tree, folder_item), "playlist")
|
|
assert tree._drop_target is None
|
|
|
|
|
|
class TestKindAwareTick:
|
|
def test_tick_clears_folder_highlight_scrolled_out_from_under_cursor(
|
|
self, qapp, tmp_path):
|
|
manager, tree = _tree(qapp, tmp_path, height=100)
|
|
folder = manager.create_folder("aaa folder") # sorts to the top row
|
|
dragged = None
|
|
for i in range(60):
|
|
dragged = manager.create_playlist(f"playlist {i}")
|
|
tree.setCurrentItem(tree._find_item(dragged.persistent_id))
|
|
tree.scrollToTop() # setCurrentItem scrolled the dragged row into view
|
|
|
|
pos = _pos_over(tree, tree._find_item(folder.persistent_id))
|
|
tree._highlight_drop_target(pos, "folder")
|
|
assert tree._drop_target is not None
|
|
|
|
# Cursor held still over that spot while the edge auto-scroll rolls:
|
|
# the folder scrolls away and a non-target row takes its place.
|
|
tree._drag_target_kind = "folder"
|
|
tree._autoscroll_pos = pos
|
|
tree._autoscroll_dir = 1
|
|
assert tree.verticalScrollBar().value() == 0
|
|
tree._autoscroll_tick()
|
|
|
|
assert tree.verticalScrollBar().value() == 1
|
|
assert tree._drop_target is None
|
|
|
|
def test_stop_autoscroll_resets_drag_target_kind(self, qapp, tmp_path):
|
|
_manager, tree = _tree(qapp, tmp_path)
|
|
tree._drag_target_kind = "folder"
|
|
tree._stop_autoscroll()
|
|
assert tree._drag_target_kind is None
|