main.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  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. from PyQt5.QtWidgets import (
  22. QApplication,
  23. QMainWindow,
  24. QMenuBar,
  25. QMenu,
  26. QWidget,
  27. QHBoxLayout,
  28. QComboBox,
  29. QSplitter,
  30. QSizePolicy,
  31. QStyle
  32. )
  33. from PyQt5.QtCore import (
  34. QCoreApplication,
  35. Qt,
  36. pyqtSignal,
  37. QSettings
  38. )
  39. import wiizard.globals as Global
  40. from wiizard.translations import _
  41. from wiizard.devices import DevicesModel as _DevicesModel
  42. from wiizard.thread import THREAD_FLAG_IS_CANCELLABLE, THREAD_FLAG_IS_STOPABLE
  43. from wiizard.models.source import _Sources
  44. from wiizard.views.source import SourceWidget
  45. from wiizard.views.about import AboutDialog
  46. from wiizard.views.operations import ApplyChangedDialog
  47. from wiizard.images import _SharedGameImages, AppResources
  48. from wiizard.const import (
  49. IMG_FRONT_COVER,
  50. IMG_FRONT_3D_COVER,
  51. IMG_DISC
  52. )
  53. from wiizard.views.images import DlImagesDialog
  54. from wiizard.views.settings import SettingsDialog
  55. from wiizard.models.settings import GameImageSettings
  56. from wiizard.views.manageLocalSources import ManageLocalWidget
  57. from wiizard.views.formatDialog import FormatDialog
  58. class SourcesView(QWidget):
  59. gamesUpdated = pyqtSignal() # Re-emit from any of the models
  60. def __init__(self, parent=None):
  61. QWidget.__init__(self, parent=parent)
  62. self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
  63. layout = QHBoxLayout(self)
  64. splitter = QSplitter(Qt.Horizontal, self)
  65. self.sourceA = SourceWidget(self)
  66. self.sourceB = SourceWidget(self)
  67. splitter.addWidget(self.sourceA)
  68. splitter.addWidget(self.sourceB)
  69. layout.addWidget(splitter)
  70. self.sourceA.listView.gamesUpdated.connect(self.gamesUpdated)
  71. self.sourceB.listView.gamesUpdated.connect(self.gamesUpdated)
  72. def getActiveModels(self):
  73. """ Returns current SourceModels used by either the left or the right view.
  74. """
  75. models = []
  76. sourceA = self.sourceA.listView.model()
  77. sourceB = self.sourceB.listView.model()
  78. if sourceA is not None:
  79. models.append(sourceA.sourceModel)
  80. if sourceB is not None:
  81. models.append(sourceB.sourceModel)
  82. return models
  83. class MainWindow(QMainWindow):
  84. def __init__(self, requestHandler, scraper):
  85. QMainWindow.__init__(self)
  86. AppResources.resolvePaths()
  87. self.setWindowIcon(AppResources.getIcon("wiizard_icon_32.png"))
  88. self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
  89. self.__requestHandler = requestHandler
  90. self.__scraper = scraper
  91. Global.SharedGameImages = _SharedGameImages()
  92. Global.DevicesModel = _DevicesModel()
  93. Global.DevicesModel.init()
  94. Global.Sources = _Sources()
  95. """ Menu bar """
  96. self.menubar = QMenuBar(self)
  97. # -- File menu --
  98. menufile = QMenu("File", self)
  99. menufile.addAction("Exit", self.__exit)
  100. menufile = QMenu(_("File"), self)
  101. menufile.addAction(_("Exit"), self.__exit)
  102. self.menubar.addMenu(menufile)
  103. # -- Settings --
  104. self.menubar.addAction(_("Settings"), self.__onSettingsClicked)
  105. # -- About --
  106. self.menubar.addAction(_("About"), self.__onAboutClicked)
  107. self.setMenuBar(self.menubar)
  108. """ Toolbar """
  109. toolbar = self.addToolBar(_("Toolbar"))
  110. self.dlMissingImgAction = toolbar.addAction(_("Download missing images"))
  111. self.dlMissingImgAction.setIcon(QApplication.style().standardIcon(QStyle.SP_ArrowDown))
  112. self.manageLocalAction = toolbar.addAction(_("Local source"))
  113. self.manageLocalAction.setToolTip(_("Manage local sources"))
  114. self.manageLocalAction.setIcon(QApplication.style().standardIcon(QStyle.SP_DirIcon))
  115. self.applyAction = toolbar.addAction(_("Apply"))
  116. self.applyAction.setToolTip(_("Apply changes"))
  117. self.applyAction.setIcon(QApplication.style().standardIcon(QStyle.SP_DialogApplyButton))
  118. self.applyAction.setEnabled(False)
  119. self.thumbSizeCombo = QComboBox(self)
  120. self.thumbSizeCombo.setToolTip(_("Icon size"))
  121. self.thumbSizeCombo.addItem(_("Large"))
  122. self.thumbSizeCombo.addItem(_("Medium"))
  123. self.thumbSizeCombo.addItem(_("Small"))
  124. self.thumbSizeCombo.setCurrentIndex(0)
  125. toolbar.addWidget(self.thumbSizeCombo)
  126. self.thumbTypeCombo = QComboBox(self)
  127. self.thumbTypeCombo.setToolTip(_("Icon type"))
  128. self.thumbTypeCombo.addItem(_("2D"))
  129. self.thumbTypeCombo.addItem(_("3D"))
  130. self.thumbTypeCombo.addItem(_("Disc"))
  131. self.thumbTypeCombo.addItem(_("None"))
  132. self.thumbTypeCombo.setCurrentIndex(1)
  133. toolbar.addWidget(self.thumbTypeCombo)
  134. self.formatAction = toolbar.addAction(_("Format"))
  135. self.formatAction.setIcon(QApplication.style().standardIcon(QStyle.SP_DialogResetButton))
  136. self.loadSettings()
  137. self.sourcesView = SourcesView(self)
  138. self.setCentralWidget(self.sourcesView)
  139. """ Make connections to signals """
  140. self.dlMissingImgAction.triggered.connect(self.dlMissingImgClicked)
  141. self.manageLocalAction.triggered.connect(self.manageLocal)
  142. self.applyAction.triggered.connect(self.onApply)
  143. self.thumbSizeCombo.currentIndexChanged.connect(self.onThumbSizeChange)
  144. self.thumbTypeCombo.currentIndexChanged.connect(self.onThumbTypeChange)
  145. self.sourcesView.gamesUpdated.connect(self.onGamesUpdated)
  146. self.formatAction.triggered.connect(self.__onFormatClicked)
  147. def __exit(self):
  148. QCoreApplication.exit(0)
  149. def __onAboutClicked(self):
  150. dialog = AboutDialog(parent=self)
  151. dialog.exec()
  152. def __onSettingsClicked(self):
  153. dialog = SettingsDialog(parent=self)
  154. dialog.exec()
  155. def closeEvent(self, event=None):
  156. # Disable everything.
  157. self.setEnabled(False)
  158. # Wait on threads
  159. for thread in Global.ActiveThreads:
  160. if (thread.flags & THREAD_FLAG_IS_CANCELLABLE):
  161. thread.cancel()
  162. elif (thread.flags & THREAD_FLAG_IS_STOPABLE):
  163. thread.stop()
  164. thread.wait()
  165. # Stop device monitoring thread
  166. Global.DevicesModel.stop()
  167. # Save settings
  168. self.saveSettings()
  169. def saveSettings(self):
  170. settings = QSettings("CYBERDEViL/Wiizard/", "config", self)
  171. settings.setValue("Sources", Global.Sources.serialize())
  172. settings.setValue("GameImageSettings", GameImageSettings.serialize())
  173. def loadSettings(self):
  174. settings = QSettings("CYBERDEViL/Wiizard/", "config", self)
  175. Global.Sources.deserialize(settings.value("Sources", dict(), dict))
  176. GameImageSettings.deserialize(settings.value("GameImageSettings",
  177. dict(), dict))
  178. def dlMissingImgClicked(self, checked=False):
  179. self.dlMissingImgAction.setEnabled(False)
  180. self.__dlMissingDialog = DlImagesDialog(self.__requestHandler, self.__scraper, parent=self)
  181. self.__dlMissingDialog.finished.connect(self.dlMissingImgClosed)
  182. self.__dlMissingDialog.show()
  183. def dlMissingImgClosed(self):
  184. self.__dlMissingDialog = None # dereference
  185. self.dlMissingImgAction.setEnabled(True)
  186. def manageLocal(self):
  187. dialog = ManageLocalWidget(parent=self)
  188. dialog.exec()
  189. def onThumbSizeChange(self, index):
  190. if index == 0 : # large
  191. Global.SharedGameImages.setImgSize(Global.SharedGameImages.SIZE_LARGE)
  192. elif index == 1: # medium
  193. Global.SharedGameImages.setImgSize(Global.SharedGameImages.SIZE_MEDIUM)
  194. elif index == 2: # small
  195. Global.SharedGameImages.setImgSize(Global.SharedGameImages.SIZE_SMALL)
  196. def onThumbTypeChange(self, index):
  197. if index == 0 : # 2D
  198. Global.SharedGameImages.setImgType(IMG_FRONT_COVER)
  199. elif index == 1: # 3D
  200. Global.SharedGameImages.setImgType(IMG_FRONT_3D_COVER)
  201. elif index == 2: # Disc
  202. Global.SharedGameImages.setImgType(IMG_DISC)
  203. else:
  204. Global.SharedGameImages.setImgType(None)
  205. def onGamesUpdated(self):
  206. """ One of the active sourceModels updated their games
  207. - update the apply button state """
  208. sourceModels = self.sourcesView.getActiveModels()
  209. for sourceModel in sourceModels:
  210. if sourceModel.hasPendingChanges():
  211. self.applyAction.setEnabled(True)
  212. return
  213. self.applyAction.setEnabled(False)
  214. def onApply(self):
  215. """ Apply button pressed """
  216. dialog = ApplyChangedDialog(self.sourcesView.getActiveModels(), parent=self)
  217. dialog.exec()
  218. def __onFormatClicked(self):
  219. dialog = FormatDialog(parent=self)
  220. dialog.exec()