A bit of a refactor, attempt to be nicer to servers and still load quickly.
This commit is contained in:
@@ -32,7 +32,7 @@ def setupLogging():
|
|||||||
format='%(message)s [%(asctime)s]',
|
format='%(message)s [%(asctime)s]',
|
||||||
datefmt='%a %b %d %I:%M:%S %p %Z %Y',
|
datefmt='%a %b %d %I:%M:%S %p %Z %Y',
|
||||||
handlers=[
|
handlers=[
|
||||||
logging.FileHandler(logFile, encoding='utf-8'),
|
logging.FileHandler(logFile, mode='w', encoding='utf-8'),
|
||||||
logging.StreamHandler(sys.stdout)
|
logging.StreamHandler(sys.stdout)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|||||||
+553
-191
File diff suppressed because it is too large
Load Diff
@@ -18,6 +18,7 @@ from PySide6.QtWidgets import (
|
|||||||
QPushButton,
|
QPushButton,
|
||||||
QTreeWidgetItem,
|
QTreeWidgetItem,
|
||||||
QVBoxLayout,
|
QVBoxLayout,
|
||||||
|
QAbstractItemView,
|
||||||
)
|
)
|
||||||
|
|
||||||
from src.api.client import SubsonicClient
|
from src.api.client import SubsonicClient
|
||||||
@@ -75,6 +76,12 @@ class SearchDialog(QDialog):
|
|||||||
self.resultsTree.setHeaderHidden(True)
|
self.resultsTree.setHeaderHidden(True)
|
||||||
self.resultsTree.setRootIsDecorated(True)
|
self.resultsTree.setRootIsDecorated(True)
|
||||||
self.resultsTree.setContextMenuPolicy(Qt.CustomContextMenu)
|
self.resultsTree.setContextMenuPolicy(Qt.CustomContextMenu)
|
||||||
|
self.resultsTree.setUniformRowHeights(True)
|
||||||
|
self.resultsTree.setItemsExpandable(True)
|
||||||
|
self.resultsTree.setExpandsOnDoubleClick(True)
|
||||||
|
self.resultsTree.setSelectionMode(QAbstractItemView.SingleSelection)
|
||||||
|
self.resultsTree.setAllColumnsShowFocus(True)
|
||||||
|
self.resultsTree.setFocusPolicy(Qt.StrongFocus)
|
||||||
self.resultsTree.itemActivated.connect(self.handleActivate)
|
self.resultsTree.itemActivated.connect(self.handleActivate)
|
||||||
self.resultsTree.itemExpanded.connect(self.onItemExpanded)
|
self.resultsTree.itemExpanded.connect(self.onItemExpanded)
|
||||||
self.resultsTree.customContextMenuRequested.connect(self.showContextMenu)
|
self.resultsTree.customContextMenuRequested.connect(self.showContextMenu)
|
||||||
@@ -112,10 +119,7 @@ class SearchDialog(QDialog):
|
|||||||
self.populateResults(results)
|
self.populateResults(results)
|
||||||
|
|
||||||
if self._resultsLoaded:
|
if self._resultsLoaded:
|
||||||
self.resultsTree.setFocus()
|
self._focusFirstResult()
|
||||||
first = self.resultsTree.topLevelItem(0)
|
|
||||||
if first:
|
|
||||||
self.resultsTree.setCurrentItem(first)
|
|
||||||
self.statusLabel.setText("Search complete. Use the tree to play or queue items.")
|
self.statusLabel.setText("Search complete. Use the tree to play or queue items.")
|
||||||
else:
|
else:
|
||||||
self.statusLabel.setText("No results found.")
|
self.statusLabel.setText("No results found.")
|
||||||
@@ -123,6 +127,15 @@ class SearchDialog(QDialog):
|
|||||||
AccessibleTextDialog.showError("Search Error", str(e), parent=self)
|
AccessibleTextDialog.showError("Search Error", str(e), parent=self)
|
||||||
self.statusLabel.setText("Search failed.")
|
self.statusLabel.setText("Search failed.")
|
||||||
|
|
||||||
|
def _focusFirstResult(self):
|
||||||
|
"""Move focus to the first result item for immediate keyboard navigation."""
|
||||||
|
self.resultsTree.setFocus(Qt.OtherFocusReason)
|
||||||
|
first = self.resultsTree.topLevelItem(0)
|
||||||
|
if not first:
|
||||||
|
return
|
||||||
|
target = first.child(0) if first.childCount() else first
|
||||||
|
self.resultsTree.setCurrentItem(target)
|
||||||
|
|
||||||
def populateResults(self, results: Dict[str, List]):
|
def populateResults(self, results: Dict[str, List]):
|
||||||
"""Populate the results tree from search3 data."""
|
"""Populate the results tree from search3 data."""
|
||||||
artists: List[Artist] = results.get("artists", [])
|
artists: List[Artist] = results.get("artists", [])
|
||||||
@@ -131,8 +144,7 @@ class SearchDialog(QDialog):
|
|||||||
|
|
||||||
if artists:
|
if artists:
|
||||||
root = QTreeWidgetItem([f"Artists ({len(artists)})"])
|
root = QTreeWidgetItem([f"Artists ({len(artists)})"])
|
||||||
root.setData(0, Qt.UserRole, {"type": "artists_root"})
|
root.setData(0, Qt.UserRole, {"type": "artists_root", "loaded": True})
|
||||||
root.setExpanded(True)
|
|
||||||
for artist in artists:
|
for artist in artists:
|
||||||
item = QTreeWidgetItem([artist.name])
|
item = QTreeWidgetItem([artist.name])
|
||||||
item.setData(0, Qt.UserRole, {"type": "artist", "id": artist.id, "artist": artist, "loaded": False})
|
item.setData(0, Qt.UserRole, {"type": "artist", "id": artist.id, "artist": artist, "loaded": False})
|
||||||
@@ -140,12 +152,12 @@ class SearchDialog(QDialog):
|
|||||||
item.addChild(QTreeWidgetItem(["Loading..."]))
|
item.addChild(QTreeWidgetItem(["Loading..."]))
|
||||||
root.addChild(item)
|
root.addChild(item)
|
||||||
self.resultsTree.addTopLevelItem(root)
|
self.resultsTree.addTopLevelItem(root)
|
||||||
|
self.resultsTree.expandItem(root)
|
||||||
self._resultsLoaded = True
|
self._resultsLoaded = True
|
||||||
|
|
||||||
if albums:
|
if albums:
|
||||||
root = QTreeWidgetItem([f"Albums ({len(albums)})"])
|
root = QTreeWidgetItem([f"Albums ({len(albums)})"])
|
||||||
root.setData(0, Qt.UserRole, {"type": "albums_root"})
|
root.setData(0, Qt.UserRole, {"type": "albums_root", "loaded": True})
|
||||||
root.setExpanded(True)
|
|
||||||
for album in albums:
|
for album in albums:
|
||||||
label = f"{album.name} — {album.artist} ({album.songCount} tracks)"
|
label = f"{album.name} — {album.artist} ({album.songCount} tracks)"
|
||||||
item = QTreeWidgetItem([label])
|
item = QTreeWidgetItem([label])
|
||||||
@@ -154,6 +166,7 @@ class SearchDialog(QDialog):
|
|||||||
item.addChild(QTreeWidgetItem(["Loading..."]))
|
item.addChild(QTreeWidgetItem(["Loading..."]))
|
||||||
root.addChild(item)
|
root.addChild(item)
|
||||||
self.resultsTree.addTopLevelItem(root)
|
self.resultsTree.addTopLevelItem(root)
|
||||||
|
self.resultsTree.expandItem(root)
|
||||||
self._resultsLoaded = True
|
self._resultsLoaded = True
|
||||||
|
|
||||||
if songs:
|
if songs:
|
||||||
@@ -164,8 +177,7 @@ class SearchDialog(QDialog):
|
|||||||
if not songs:
|
if not songs:
|
||||||
return
|
return
|
||||||
root = QTreeWidgetItem([rootLabel])
|
root = QTreeWidgetItem([rootLabel])
|
||||||
root.setData(0, Qt.UserRole, {"type": "songs_root"})
|
root.setData(0, Qt.UserRole, {"type": "songs_root", "loaded": True})
|
||||||
root.setExpanded(True)
|
|
||||||
for song in songs:
|
for song in songs:
|
||||||
text = f"{song.title} — {song.artist} ({song.album}, {song.durationFormatted})"
|
text = f"{song.title} — {song.artist} ({song.album}, {song.durationFormatted})"
|
||||||
item = QTreeWidgetItem([text])
|
item = QTreeWidgetItem([text])
|
||||||
@@ -174,6 +186,7 @@ class SearchDialog(QDialog):
|
|||||||
root.addChild(item)
|
root.addChild(item)
|
||||||
self.resultsTree.addTopLevelItem(root)
|
self.resultsTree.addTopLevelItem(root)
|
||||||
self._resultsLoaded = True
|
self._resultsLoaded = True
|
||||||
|
self.resultsTree.expandItem(root)
|
||||||
|
|
||||||
def handleActivate(self, item: QTreeWidgetItem):
|
def handleActivate(self, item: QTreeWidgetItem):
|
||||||
"""Activate the selected item similar to the main tree."""
|
"""Activate the selected item similar to the main tree."""
|
||||||
@@ -230,6 +243,8 @@ class SearchDialog(QDialog):
|
|||||||
item.addChild(child)
|
item.addChild(child)
|
||||||
data["loaded"] = True
|
data["loaded"] = True
|
||||||
item.setData(0, Qt.UserRole, data)
|
item.setData(0, Qt.UserRole, data)
|
||||||
|
# Update accessibility for newly added children
|
||||||
|
self.resultsTree.updateChildAccessibility(item, True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
AccessibleTextDialog.showError("Search Error", str(e), parent=self)
|
AccessibleTextDialog.showError("Search Error", str(e), parent=self)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user