source.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675
  1. ########################################################################
  2. # Wiizard - A Wii games manager
  3. # Copyright (C) 2023 CYBERDEViL
  4. #
  5. # This file is part of Wiizard.
  6. #
  7. # Wiizard is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation, either version 3 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # Wiizard is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License
  18. # along with this program. If not, see <https://www.gnu.org/licenses/>.
  19. #
  20. ########################################################################
  21. import pickle
  22. from PyQt5.QtWidgets import (
  23. QApplication,
  24. QWidget,
  25. QListView,
  26. QSizePolicy,
  27. QLabel,
  28. QPushButton,
  29. QSpacerItem,
  30. QHBoxLayout,
  31. QVBoxLayout,
  32. QAbstractItemView,
  33. QStyle,
  34. QFrame,
  35. QComboBox,
  36. QMenu,
  37. QToolBar,
  38. QStatusBar
  39. )
  40. from PyQt5.QtCore import (
  41. Qt,
  42. pyqtSignal,
  43. QMimeData,
  44. QUrl,
  45. QTimer
  46. )
  47. from PyQt5.QtGui import (
  48. QGuiApplication,
  49. QDesktopServices,
  50. QDrag,
  51. QPainter
  52. )
  53. import wiizard.globals as Global
  54. from wiizard.models.source import (
  55. SourceSelecterModel,
  56. SourceListModel,
  57. SOURCE_TYPE_LOCAL
  58. #, SOURCE_TYPE_DEVICE, SOURCE_TYPE_FILEPART
  59. )
  60. from wiizard.views.repair import RepairDialog
  61. from wiizard.widgets.diskUsage import DiskUsageWidget
  62. from wiizard.translations import _
  63. class GamesViewMessage(QWidget):
  64. """ When a message (with optional button) needs to be displayed in
  65. 'GamesView', a instance of this class will be set as 'GamesView' it's
  66. viewport.
  67. """
  68. def __init__(self, message, buttonText=None, parent=None):
  69. QWidget.__init__(self, parent=parent)
  70. layout = QVBoxLayout(self)
  71. spacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
  72. layout.addItem(spacer)
  73. label = QLabel(message, self)
  74. label.setWordWrap(True)
  75. label.setAlignment(Qt.AlignCenter)
  76. label.setSizePolicy(QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum))
  77. layout.addWidget(label)
  78. if buttonText is not None:
  79. self.button = QPushButton(buttonText, self)
  80. self.button.setSizePolicy(QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum))
  81. layout.addWidget(self.button)
  82. layout.setAlignment(self.button, Qt.AlignHCenter)
  83. spacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
  84. layout.addItem(spacer)
  85. class InWidgetMessage(QWidget):
  86. def __init__(self, timeout=3, parent=None):
  87. QWidget.__init__(self, parent=parent)
  88. self.__timer = QTimer(self)
  89. layout = QHBoxLayout(self)
  90. self.__icon = QLabel(self)
  91. self.__label = QLabel(self)
  92. self.__label.setWordWrap(True)
  93. layout.addWidget(self.__icon)
  94. layout.addWidget(self.__label)
  95. layout.setAlignment(self.__icon, Qt.AlignTop)
  96. self.__infoPixmap = QApplication.style().standardIcon(QStyle.SP_MessageBoxInformation).pixmap(16, 16)
  97. self.__warnPixmap = QApplication.style().standardIcon(QStyle.SP_MessageBoxWarning).pixmap(16, 16)
  98. self.__errPixmap = QApplication.style().standardIcon(QStyle.SP_MessageBoxCritical).pixmap(16, 16)
  99. self.setStyleSheet("background-color: rgba(0, 0, 0, 128); "
  100. "border-style: solid; border-radius: 10px; "
  101. "border-width: 5px;")
  102. self.setAttribute(Qt.WA_TranslucentBackground, True)
  103. self.__timer.setInterval(timeout * 1000)
  104. self.__timer.setSingleShot(True)
  105. self.__timer.timeout.connect(self.hide)
  106. self.raise_()
  107. self.hide()
  108. def __show(self, message):
  109. self.setMaximumWidth(self.parent().width())
  110. self.__label.setText(message)
  111. self.adjustSize()
  112. self.show()
  113. self.__timer.start()
  114. def showMessage(self, message):
  115. self.__icon.setPixmap(self.__infoPixmap)
  116. self.__show(message)
  117. def showWarning(self, warning):
  118. self.__icon.setPixmap(self.__warnPixmap)
  119. self.__show(warning)
  120. def showError(self, error):
  121. self.__icon.setPixmap(self.__errPixmap)
  122. self.__show(error)
  123. def mousePressEvent(self, event):
  124. self.hide()
  125. # https://doc.qt.io/qt-6/qlistview.html
  126. class GamesView(QListView):
  127. """ The view for disc/games from a source.
  128. """
  129. viewModeChanged = pyqtSignal() # for updating external buttons
  130. gamesUpdated = pyqtSignal() # re-emit from current model (if any)
  131. def __init__(self, parent=None):
  132. QListView.__init__(self, parent=parent)
  133. self.messageWidget = InWidgetMessage(parent=self.parent())
  134. self.setSelectionMode(QAbstractItemView.ExtendedSelection)
  135. self.setResizeMode(QListView.Adjust)
  136. self.setViewMode(QListView.IconMode)
  137. self.setAlternatingRowColors(False)
  138. self.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel)
  139. self.setWordWrap(True)
  140. # Drag and drop stuff
  141. self.dragStartPos = None
  142. # Backup view mode
  143. self.__viewMode = QListView.IconMode
  144. # Alt view port
  145. self.__altViewport = None
  146. self.setViewport(QWidget(self))
  147. QListView.setViewMode(self, QListView.ListMode)
  148. QListView.setViewMode(self, self.__viewMode)
  149. self.reApplyDND()
  150. def resetViewport(self):
  151. if self.__altViewport:
  152. # @NOTE: setViewport will delete the previous viewport widget for us.
  153. self.setViewport(QWidget(self))
  154. self.__altViewport = None
  155. # Re-apply view mode (else drag and drop won't work)
  156. # TODO FIXME we first have to apply setViewMode(QListView.ListMode) else
  157. # drag and drop won't work (atleast on empty list)
  158. QListView.setViewMode(self, QListView.ListMode)
  159. QListView.setViewMode(self, self.__viewMode)
  160. self.reApplyDND()
  161. def rescan(self):
  162. sourceModel = self.model().sourceModel
  163. sourceModel.scanStarted.connect(self.__onStartScan)
  164. sourceModel.scanCompleted.connect(self.__onScanComplete)
  165. sourceModel.startScan()
  166. def __onStartScan(self):
  167. sourceModel = self.model().sourceModel
  168. # For now only the scan thread of local type is cancellable
  169. if sourceModel.type == SOURCE_TYPE_LOCAL:
  170. self.__altViewport = GamesViewMessage(_("Scanning.. please wait"),
  171. _("Cancel"), parent=self)
  172. self.__altViewport.button.clicked.connect(self.__onCancelScan)
  173. else:
  174. self.__altViewport = GamesViewMessage(_("Scanning.. please wait"),
  175. parent=self)
  176. self.setViewport(self.__altViewport)
  177. self.viewport().update()
  178. def __onCancelScan(self):
  179. self.model().sourceModel.cancelScan()
  180. def __onRepair(self):
  181. dialog = RepairDialog(self.model().sourceModel, parent=self)
  182. dialog.exec()
  183. def __onScanComplete(self):
  184. self.model().sourceModel.scanCompleted.disconnect(self.__onScanComplete)
  185. self.model().sourceModel.scanStarted.disconnect(self.__onStartScan)
  186. if self.model().sourceModel.hasErrors():
  187. self.__altViewport = GamesViewMessage(
  188. _("Source has {} errors, please repair them first.")
  189. .format(self.model().sourceModel.hasErrors()), "Repair", self)
  190. self.setViewport(self.__altViewport)
  191. self.__altViewport.button.clicked.connect(self.__onRepair)
  192. else:
  193. self.resetViewport()
  194. def setModel(self, model=None):
  195. # Disconnect previous connection
  196. curModel = self.model()
  197. if curModel is not None:
  198. curModel.sourceModel.gamesUpdated.disconnect(self.gamesUpdated)
  199. self.resetViewport()
  200. QListView.setModel(self, model)
  201. if model is not None:
  202. model.sourceModel.gamesUpdated.connect(self.gamesUpdated)
  203. self.rescan()
  204. def paintEvent(self, event):
  205. # Alt view is active, don't paint anything.
  206. if self.__altViewport:
  207. return
  208. model = self.model()
  209. if model is None:
  210. painter = QPainter(self.viewport())
  211. painter.setPen(Qt.white)
  212. painter.drawText(self.rect(), Qt.AlignCenter, _("No source selected"))
  213. return
  214. if not model.rowCount():
  215. painter = QPainter(self.viewport())
  216. painter.setPen(Qt.white)
  217. painter.drawText(self.rect(), Qt.AlignCenter, _("No games found"))
  218. return
  219. QListView.paintEvent(self, event)
  220. def dropEvent(self, dropEvent):
  221. print("Drop!")
  222. rows = pickle.loads(dropEvent.mimeData().data("pywbfs/index-list"))
  223. sourceModel = dropEvent.source().model()
  224. sourceGames = sourceModel.filteredGames
  225. currentGames = [game.id6 for game in self.model().games]
  226. for row in rows:
  227. game = sourceGames[row]
  228. # skip games that are already present
  229. if game.id6 in currentGames:
  230. continue
  231. self.model().addFutureGame(game)
  232. print("- ", game.title)
  233. self.model().modelReset.emit()
  234. def dragMoveEvent(self, dragMoveEvent):
  235. dragMoveEvent.accept()
  236. def dragEnterEvent(self, dragEnterEvent):
  237. if self.model() is None:
  238. self.messageWidget.showWarning(_("No model selected, nothing to drop "
  239. "on."))
  240. return
  241. if self.model().sourceModel.isScanning():
  242. self.messageWidget.showWarning(_("Will not drop while scanning."))
  243. return
  244. # Don't allow to drop on the same model
  245. if dragEnterEvent.source().model() == self.model():
  246. return
  247. # Non supported Mimetype
  248. if not dragEnterEvent.mimeData().hasFormat("pywbfs/index-list"): # TODO change mimetype
  249. return
  250. # Don't allow drop when all games are already present
  251. rows = pickle.loads(dragEnterEvent.mimeData().data("pywbfs/index-list"))
  252. sourceModel = dragEnterEvent.source().model()
  253. sourceGames = sourceModel.filteredGames
  254. currentGames = [game.id6 for game in self.model().games]
  255. allGamesPresent = True
  256. addSize = 100
  257. for row in rows:
  258. game = sourceGames[row]
  259. if game.id6 not in currentGames:
  260. allGamesPresent = False
  261. addSize += game.fileSize
  262. if allGamesPresent:
  263. self.messageWidget.showWarning(_("Drop denied: all games are already "
  264. "here."))
  265. return
  266. if addSize >= self.model().sourceModel.getFutureFree():
  267. self.messageWidget.showWarning(_("Drop denied: not enough free space."))
  268. return
  269. dragEnterEvent.acceptProposedAction()
  270. def mousePressEvent(self, mousePressEvent):
  271. if mousePressEvent.button() == Qt.LeftButton:
  272. self.dragStartPos = mousePressEvent.pos()
  273. QListView.mousePressEvent(self, mousePressEvent)
  274. def mouseMoveEvent(self, mouseMoveEvent):
  275. if not (mouseMoveEvent.buttons() & Qt.LeftButton):
  276. return
  277. if (mouseMoveEvent.pos() - self.dragStartPos).manhattanLength() < QApplication.startDragDistance():
  278. return
  279. selectionModel = self.selectionModel()
  280. if selectionModel is None:
  281. return
  282. if not selectionModel.hasSelection():
  283. return
  284. # Selected indexes
  285. indexes = selectionModel.selectedRows()
  286. # Create drag event
  287. # https://doc.qt.io/qt-5/dnd.html
  288. drag = QDrag(self)
  289. data = QMimeData()
  290. data.setData("pywbfs/index-list", bytearray(pickle.dumps([index.row() for index in indexes])))
  291. drag.setMimeData(data)
  292. # Only 1 selected, use disc image as icon
  293. if len(indexes) == 1 and Global.SharedGameImages.imgType is not None:
  294. index = indexes[0]
  295. model = self.model()
  296. id6Str = str(model.filteredGames[index.row()].id6)
  297. icon = Global.SharedGameImages.getGameImage(id6Str)
  298. drag.setPixmap(icon)
  299. drop = drag.exec(Qt.CopyAction)
  300. def reApplyDND(self):
  301. self.setAcceptDrops(True)
  302. self.setDragEnabled(True)
  303. self.setDefaultDropAction(Qt.IgnoreAction)
  304. self.setDragDropMode(QAbstractItemView.DragDrop)
  305. def toggleViewMode(self):
  306. if self.viewMode() == QListView.IconMode:
  307. self.setViewMode(QListView.ListMode)
  308. else:
  309. self.setViewMode(QListView.IconMode)
  310. self.update()
  311. def setViewMode(self, mode):
  312. self.__viewMode = mode
  313. QListView.setViewMode(self, mode)
  314. if mode == QListView.ListMode:
  315. self.setAlternatingRowColors(True)
  316. self.reApplyDND()
  317. else:
  318. self.setAlternatingRowColors(False)
  319. self.viewModeChanged.emit()
  320. def contextMenuEvent(self, contextMenuEvent):
  321. selectionModel = self.selectionModel()
  322. if selectionModel is None:
  323. return
  324. menu = QMenu(self)
  325. toggleViewModeAction = menu.addAction(_("Toggle view mode"))
  326. copyTitleAction = -1
  327. copyPathAction = -1
  328. copyId6Action = -1
  329. markDeletionAction = -1
  330. delDeletionAction = -1
  331. openInFileBrowser = -1
  332. if selectionModel.hasSelection():
  333. copyTitleAction = menu.addAction(_("Copy title(s)"))
  334. copyPathAction = menu.addAction(_("Copy path(s)"))
  335. copyId6Action = menu.addAction(_("Copy ID6(s)"))
  336. markDeletion = False
  337. clearDeletion = False
  338. for index in selectionModel.selectedRows():
  339. game = self.model().filteredGames[index.row()]
  340. if self.model().sourceModel.isGameMarkedForDeletion(game):
  341. clearDeletion = True
  342. else:
  343. markDeletion = True
  344. # Only add "Mark for deletion" when there are games selected that are not
  345. # yet marked for deletion.
  346. if markDeletion:
  347. markDeletionAction = menu.addAction(_("Mark for deletion"))
  348. # Only add "Clear deletion" when there are games selected that are marked
  349. # for deletion.
  350. if clearDeletion:
  351. delDeletionAction = menu.addAction(_("Clear deletion"))
  352. # "Open in filebrowser" only available when 1 game is selected
  353. if len(selectionModel.selectedRows()) == 1:
  354. openInFileBrowser = menu.addAction(_("Open in filebrowser"))
  355. # Exec the menu
  356. action = menu.exec_(self.mapToGlobal(contextMenuEvent.pos()))
  357. # Handle chosen action
  358. if action == toggleViewModeAction:
  359. self.toggleViewMode()
  360. elif action == copyTitleAction:
  361. indexes = selectionModel.selectedRows()
  362. titles = ""
  363. for index in indexes:
  364. titles += self.model().filteredGames[index.row()].title + "\n"
  365. clipboard = QGuiApplication.clipboard()
  366. clipboard.setText(titles)
  367. elif action == copyPathAction:
  368. indexes = selectionModel.selectedRows()
  369. paths = ""
  370. for index in indexes:
  371. paths += self.model().filteredGames[index.row()].sourcePath + "\n"
  372. clipboard = QGuiApplication.clipboard()
  373. clipboard.setText(paths)
  374. elif action == copyId6Action:
  375. indexes = selectionModel.selectedRows()
  376. id6s = ""
  377. for index in indexes:
  378. id6s += str(self.model().filteredGames[index.row()].id6) + "\n"
  379. clipboard = QGuiApplication.clipboard()
  380. clipboard.setText(id6s)
  381. elif action == markDeletionAction:
  382. # @NOTE: make sure indexes are sorted desc when deleting stuff
  383. model = self.model()
  384. indexes = [index.row() for index in selectionModel.selectedRows()]
  385. indexes.sort(reverse=True)
  386. for index in indexes:
  387. model.addGameForDeletion(index)
  388. elif action == delDeletionAction:
  389. # @NOTE: make sure indexes are sorted desc when deleting stuff
  390. model = self.model()
  391. indexes = [index.row() for index in selectionModel.selectedRows()]
  392. indexes.sort(reverse=True)
  393. for index in indexes:
  394. model.delGameFromDeletion(index)
  395. elif action == openInFileBrowser:
  396. index = selectionModel.selectedRows()[0]
  397. path = self.model().filteredGames[index.row()].sourcePath
  398. QDesktopServices.openUrl(QUrl("file://" + path, QUrl.TolerantMode))
  399. class SourceSelecterWidget(QComboBox):
  400. indexChanged = pyqtSignal(int) # Not emitted on modelReset unless a selected
  401. # sourceModel got deleted.
  402. def __init__(self, parent=None):
  403. QComboBox.__init__(self, parent=parent)
  404. # Automatic resize width of drop down list
  405. sizePolicy = self.view().sizePolicy()
  406. sizePolicy.setHorizontalPolicy(QSizePolicy.MinimumExpanding)
  407. self.view().setSizePolicy(sizePolicy)
  408. # Automatic expand width of self
  409. sizePolicy = self.sizePolicy()
  410. sizePolicy.setHorizontalPolicy(QSizePolicy.MinimumExpanding)
  411. self.setSizePolicy(sizePolicy)
  412. model = SourceSelecterModel() # TODO there just needs to be 1 instance
  413. self.setModel(model)
  414. self.setCurrentIndex(0) # Placeholder
  415. self.__currentSelected = None
  416. self.__modelHasReset = False
  417. self.currentIndexChanged.connect(self.__sourceChanged)
  418. model.modelReset.connect(self.__sourcesUpdated)
  419. model.preModelReset.connect(self.__preModelReset)
  420. def __sourceChanged(self, index):
  421. """ Keep track of current selected model """
  422. # Model has just reset
  423. if self.__modelHasReset is True:
  424. self.__modelHasReset = False
  425. return
  426. if index == -1:
  427. # Set to placeholder
  428. self.currentIndexChanged.disconnect(self.__sourceChanged)
  429. self.setCurrentIndex(0)
  430. self.currentIndexChanged.connect(self.__sourceChanged)
  431. self.__currentSelected = None
  432. else:
  433. # Backup selected item
  434. self.__currentSelected = self.model().getItem(index)
  435. self.indexChanged.emit(index)
  436. def __sourcesUpdated(self):
  437. """ Set new index of the current selected (if still exists) """
  438. if self.__currentSelected is not None:
  439. newIndex = self.model().getIndexForSourceModel(self.__currentSelected)
  440. self.currentIndexChanged.disconnect(self.__sourceChanged)
  441. self.setCurrentIndex(newIndex)
  442. self.__currentSelected = self.model().getItem(newIndex)
  443. if self.__currentSelected is None: # The model probably got removed, so emit
  444. self.indexChanged.emit(newIndex)
  445. self.currentIndexChanged.connect(self.__sourceChanged)
  446. else:
  447. self.__modelHasReset = False
  448. def __preModelReset(self):
  449. self.__modelHasReset = True
  450. class SourceWidget(QFrame):
  451. def __init__(self, parent=None):
  452. QFrame.__init__(self, parent=parent)
  453. self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)
  454. layout = QVBoxLayout(self)
  455. self.toolbar = QToolBar(self)
  456. self.sourceCombo = SourceSelecterWidget(self)
  457. self.toolbar.addWidget(self.sourceCombo)
  458. self.sortOrderCombo = QComboBox(self)
  459. self.sortOrderCombo.addItem(_("Asc"))
  460. self.sortOrderCombo.addItem(_("Desc"))
  461. self.sortOrderCombo.setCurrentIndex(0)
  462. self.sortOrderCombo.setEnabled(False)
  463. self.sortOrderCombo.setToolTip(_("Sort order"))
  464. self.toolbar.addWidget(self.sortOrderCombo)
  465. self.refreshAction = self.toolbar.addAction(_("Refresh location"))
  466. self.refreshAction.setToolTip(_("Refresh location"))
  467. self.refreshAction.setIcon(QApplication.style().standardIcon(QStyle.SP_BrowserReload))
  468. self.refreshAction.setEnabled(False)
  469. self.toggleViewAction = self.toolbar.addAction(_("Toggle view mode"))
  470. self.toggleViewAction.setToolTip("Toggle view mode")
  471. self.toggleViewAction.setIcon(QApplication.style().standardIcon(QStyle.SP_FileDialogListView))
  472. self.toggleViewAction.setEnabled(False)
  473. self.listView = GamesView(self)
  474. self.listView.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
  475. self.usageWidget = DiskUsageWidget(parent=self)
  476. self.statusBar = QStatusBar(self)
  477. layout.addWidget(self.toolbar, Qt.AlignTop)
  478. layout.addWidget(self.listView)
  479. layout.addWidget(self.usageWidget)
  480. layout.addWidget(self.statusBar)
  481. self.sourceCombo.indexChanged.connect(self.__sourceChanged)
  482. self.sortOrderCombo.currentIndexChanged.connect(self.__onSortOrderChange)
  483. self.refreshAction.triggered.connect(self.listView.rescan)
  484. self.toggleViewAction.triggered.connect(self.__onToggleView)
  485. self.listView.viewModeChanged.connect(self.__onListViewModeChange)
  486. def __sourceChanged(self, index):
  487. if self.listView.model() is not None:
  488. self.listView.model().sourceModel.scanStarted.disconnect(self.__onScanStarted)
  489. self.listView.model().sourceModel.scanCompleted.disconnect(self.__onScanCompleted)
  490. self.listView.model().sourceModel.gamesUpdated.disconnect(self.__onGamesUpdated)
  491. self.listView.model().sourceModel.setInUse(False)
  492. source = self.sourceCombo.model().getItem(index)
  493. if source is not None:
  494. source.setInUse(True)
  495. model = SourceListModel(source)
  496. model.setSortOrder(self.sortOrderCombo.currentIndex())
  497. self.usageWidget.updateSizes(source.getUsed(), source.getCapacity(),
  498. 0, source.getFutureFree())
  499. self.refreshAction.setEnabled(True)
  500. self.sortOrderCombo.setEnabled(True)
  501. self.toggleViewAction.setEnabled(True)
  502. source.scanStarted.connect(self.__onScanStarted)
  503. source.scanCompleted.connect(self.__onScanCompleted)
  504. source.gamesUpdated.connect(self.__onGamesUpdated)
  505. self.listView.setModel(model)
  506. else:
  507. self.refreshAction.setEnabled(False)
  508. self.sortOrderCombo.setEnabled(False)
  509. self.toggleViewAction.setEnabled(False)
  510. self.listView.setModel()
  511. self.usageWidget.updateSizes(-1, -1, -1, -1)
  512. def __onSortOrderChange(self, index):
  513. self.listView.model().setSortOrder(index)
  514. def __onScanStarted(self):
  515. #self.toolbar.setEnabled(False) # This didn't do the trick
  516. self.sourceCombo.setEnabled(False)
  517. self.refreshAction.setEnabled(False)
  518. self.sortOrderCombo.setEnabled(False)
  519. self.toggleViewAction.setEnabled(False)
  520. def __onScanCompleted(self):
  521. #self.toolbar.setEnabled(True) # This didn't do the trick
  522. self.sourceCombo.setEnabled(True)
  523. self.refreshAction.setEnabled(True)
  524. self.sortOrderCombo.setEnabled(True)
  525. self.toggleViewAction.setEnabled(True)
  526. source = self.listView.model().sourceModel
  527. discCount = len(source.games)
  528. totalSize = 0
  529. for game in source.games:
  530. totalSize += game.fileSize
  531. self.statusBar.showMessage(_(f"{discCount} Discs ({totalSize} MiB)"))
  532. def __onGamesUpdated(self):
  533. source = self.listView.model().sourceModel
  534. self.usageWidget.updateSizes(source.getFutureUsed(),
  535. source.getCapacity(),
  536. source.getFutureUsed(),
  537. source.getFutureFree()) # TODO
  538. def __onListViewModeChange(self):
  539. if self.listView.viewMode() == QListView.IconMode:
  540. self.toggleViewAction.setIcon(
  541. QApplication.style().standardIcon(QStyle.SP_FileDialogListView)
  542. )
  543. else:
  544. self.toggleViewAction.setIcon(
  545. QApplication.style().standardIcon(QStyle.SP_FileDialogDetailedView)
  546. )
  547. def __onToggleView(self):
  548. self.listView.toggleViewMode()