-
Notifications
You must be signed in to change notification settings - Fork 0
/
RetroManager.py
378 lines (302 loc) · 15.6 KB
/
RetroManager.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
#GUI imports
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
#RetroManager imports
from RetroManagerDatabase import *
from RetroManagerCore import *
from RetroManagerDevice import RetroManagerDevice
import psutil
import configparser
import os
import logging
# List of devices that we're detected on last scan, this is used to compare to find any new devices
driveHistory = []
# this is a list of the open devicetabs
openDevices = []
RMCore = RetroManagerCore()
static_LoggingPath = os.path.join(os.path.expanduser("~"), "RetroManager", "retromanager.log")
#SubClass QMainWindow to create a Tadpole general interface
class MainWindow (QMainWindow):
library_columns_Title = "Title"
library_columns_Console = "Console"
library_columns_Rating = "Rating"
library_columns_Series = "Series"
library_columns_Publisher = "Publisher"
library_columns = [
library_columns_Title,
library_columns_Console,
library_columns_Rating,
library_columns_Series,
library_columns_Publisher
]
def __init__(self):
super().__init__()
#General winow parameters
self.setWindowTitle("RetroManager - Handheld ROM Manager")
widget = QWidget()
self.setCentralWidget(widget)
self.setMinimumSize(800,600)
# Status Bar
self.status_bar = QStatusBar()
self.setStatusBar(self.status_bar)
#Load the ribbon Menus
self.loadMenus()
self.loadToolbar()
self.layout = QGridLayout(widget)
#Create the tabs widget and add it to the main layout
self.tabs = QTabWidget()
self.layout.addWidget(self.tabs)
#Create Library Tab
self.tab_library = QWidget()
self.tabs.addTab(self.tab_library, "Library")
self.tab_library.layout = QGridLayout(self.tab_library)
self.tab_library.gameslist = []
#Game Table Widget
self.tab_library.tbl_gamelist = QTableWidget()
self.tab_library.tbl_gamelist.setColumnCount(5)
self.tab_library.tbl_gamelist.setHorizontalHeaderLabels(self.library_columns)
self.tab_library.tbl_gamelist.horizontalHeader().setSectionResizeMode(0,QHeaderView.ResizeToContents)
self.tab_library.tbl_gamelist.horizontalHeader().setSectionResizeMode(1,QHeaderView.ResizeToContents)
self.tab_library.layout.addWidget(self.tab_library.tbl_gamelist)
self.tab_library.tbl_gamelist.itemChanged.connect(self.catchTableItemChanged)
self.tab_library.tbl_gamelist.show()
# Right Click Menu
self.tab_library.tbl_gamelist.setContextMenuPolicy(Qt.CustomContextMenu)
self.tab_library.tbl_gamelist.customContextMenuRequested.connect(self.loadRightClickMenu_LibraryTable)
"""
self.tblview_gamelist = QTableView()
self.tblGameModel = rmGameTableModel()
#TODO set gamemodel data
self.tblview_gamelist.setModel(self.tblGameModel)
self.tblview_gamelist.show();
"""
#load All Game Data on Opening
self.loadAllGamesToLibraryTable()
def toggle_features(enable: bool):
"""Toggles program features on or off"""
features = []
for feature in features:
feature.setEnabled(enable)
def loadMenus(self):
#File Menu
self.menu_file = self.menuBar().addMenu("&File")
action_about = QAction("&About", self, triggered=self.about)
self.menu_file.addAction(action_about)
action_openDevice = QAction("Open Device", self, triggered=self.menu_openDevice)
self.menu_file.addAction(action_openDevice)
action_exit = QAction("E&xit", self, shortcut="Ctrl+Q",triggered=self.close)
self.menu_file.addAction(action_exit)
self.action_Test = QAction("Test Function", self,triggered=self.testFunction)
self.menu_file.addAction(self.action_Test)
#Device Menu
self.menu_device = self.menuBar().addMenu("&Device")
self.action_ImportSaves = QAction("Import Saves", self, triggered=self.importSavesFromDevice)
self.menu_device.addAction(self.action_ImportSaves)
def loadToolbar(self):
toolbar = QToolBar("Toolbar")
toolbar.toggleViewAction().setEnabled(False)
toolbar.setIconSize(QSize(64,64))
self.addToolBar(toolbar)
# TODO Open Device button?
# Add ROMs
self.toolbaraction_AddROM = QAction(QIcon("icons/addrom.png"), "Add new ROMs to your library", self, triggered=self.importGames)
self.toolbaraction_AddROM.setStatusTip("Add new ROMs to your library")
toolbar.addAction(self.toolbaraction_AddROM)
# Sync Saves
self.toolbaraction_syncDevice = QAction(QIcon("icons/devicesync.png"), "Sync Saves with Library", self, triggered=self.syncOpenDevice)
self.toolbaraction_syncDevice.setStatusTip("Sync Device with Library")
toolbar.addAction(self.toolbaraction_syncDevice)
# Donate
self.toolbaraction_donate = QAction(QIcon("icons/donate.png"), "Donate", self, triggered=self.ToDoBox)
self.toolbaraction_donate.setStatusTip("Donate to support RetroManager Development")
toolbar.addAction(self.toolbaraction_donate)
# Settings
self.toolbaraction_settings = QAction(QIcon("icons/settings.png"), "Settings", self, triggered=self.ToDoBox)
self.toolbaraction_settings.setStatusTip("Open Settings")
toolbar.addAction(self.toolbaraction_settings)
def loadRightClickMenu_LibraryTable(self, pos):
menu = QMenu()
activeTab = self.tabs.currentWidget()
#Send to Device
rightClick_sendToDevice = menu.addMenu('Send to Device')
if len(openDevices) == 0:
device_option = rightClick_sendToDevice.addAction("No Devices Connected")
device_option.setEnabled(False)
else:
for tab_device in openDevices:
name = tab_device.device.name if tab_device.device.name!="" else tab_device.device.mountpoint
device_option = rightClick_sendToDevice.addAction(f"{name}")
device_option.triggered.connect(lambda checked, tab_device=tab_device: self.sendGamesToDevice(tab_device))
# Position
menu.exec_(activeTab.mapToGlobal(pos))
def loadRightClickMenu_DeviceTable(self, pos):
menu = QMenu()
activeTab = self.tabs.currentWidget()
rightClick_sendToLibrary = menu.addAction("Send to Library")
rightClick_sendToLibrary.triggered.connect(self.sendGamesToLibrary)
rightClick_openInfo = menu .addAction("Open Details")
rightClick_openInfo.triggered.connect(self.openDetails(False))
# Position
menu.exec_(activeTab.mapToGlobal(pos))
def syncOpenDevice(self):
print("syncing device with library")
def ToDoBox(self):
msg = QMessageBox(self)
msg.setWindowTitle("Under Development")
msg.setText("This Feature is still under development.")
msg.exec_()
def sendGamesToDevice(self, tab_device):
#TODO add the loading window
activeTab = self.tabs.currentWidget()
for item in activeTab.tbl_gamelist.selectedIndexes():
rmgameitem = activeTab.gameslist[item.row()]
logging.info(f"RetroManager~sendGamesToDevice: Selected Game - {rmgameitem.title}")
tab_device.device.sendGameToDevice(rmgameitem)
#Refresh the device table
self.reloadTable(tab_device, tab_device.device.scanForGames())
def sendGamesToLibrary(self):
activeTab = self.tabs.currentWidget()
gamesToAdd = []
for item in activeTab.tbl_gamelist.selectedIndexes():
rmgameitem = activeTab.gameslist[item.row()]
logging.info(f"RetroManager~sendGamesToLibrary: Selected Game - {rmgameitem.title}")
RMCore.importGame(rmgameitem)
#Refresh the library table
self.loadAllGamesToLibraryTable()
return True
def openDetails(self, edittable = False):
activeTab = self.tabs.currentWidget()
gamesToAdd = []
for item in activeTab.tbl_gamelist.selectedIndexes():
rmgameitem = activeTab.gameslist[item.row()]
logging.info(f"RetroManager~openDetails: Selected Game - {rmgameitem.title}")
#TODO open a dialog showing the game details
def importSavesFromDevice(self):
# TODO !!!!!!!!!!!!!!!!
activeTab = self.tabs.currentWidget()
# Check that the active tab is not the library
logging.info(f"RetroManager~importSavesFromDevice: {activeTab.device.name}")
# Get the list of saves from the device
RMCore.importSaves(activeTab.device)
return True
def handle_TableEdit(self, tableWidget):
logging.info(f"RetroManager~handle_TableEdit: table widget changed {tableWidget}")
def about(self):
QMessageBox.about(self, "About RetroManager","RetroManager was created by EricGoldstein because he got sick of losing all his saves")
def testFunction(self):
print("Running Test Function")
print(f"trying to read {openDevices[0].device}")
openDevices[0].device.scanForGames()
def menu_openDevice(self):
print("Opening device")
directory = os.path.normpath(QFileDialog.getExistingDirectory()) # getExistingDirectory returns filepath slashes the wrong way around in some OS, lets fix that
if directory == '': #Check that the user actually selected a directory and didnt just close the window
return False
self.openDevice(directory)
def openDevice(self, mountpoint):
print(f"RetroManager~OpenDevice: Opening Device {mountpoint}")
device = RetroManagerDevice(mountpoint)
#Create Library Tab
tab_openDevice = QWidget()
tab_openDevice.device = device
openDevices.append(tab_openDevice)
tabname = ""
if device.name == "":
tabname = device.mountpoint
else:
tabname = device.name
self.tabs.addTab(tab_openDevice, tabname)
tab_openDevice.layout = QGridLayout(tab_openDevice)
#Game Table Widget
tab_openDevice.tbl_gamelist = QTableWidget()
tab_openDevice.tbl_gamelist.setColumnCount(5)
tab_openDevice.tbl_gamelist.setHorizontalHeaderLabels(self.library_columns)
tab_openDevice.tbl_gamelist.horizontalHeader().setSectionResizeMode(0,QHeaderView.ResizeToContents)
tab_openDevice.tbl_gamelist.horizontalHeader().setSectionResizeMode(1,QHeaderView.ResizeToContents)
tab_openDevice.layout.addWidget(tab_openDevice.tbl_gamelist)
#Disabled this as the catch function references the library table rather than pulling the details properly
tab_openDevice.tbl_gamelist.itemChanged.connect(self.catchTableItemChanged)
tab_openDevice.tbl_gamelist.show()
# Right Click Menu
tab_openDevice.tbl_gamelist.setContextMenuPolicy(Qt.CustomContextMenu)
tab_openDevice.tbl_gamelist.customContextMenuRequested.connect(self.loadRightClickMenu_DeviceTable)
#Retrieve the games from the device
self.reloadTable(tab_openDevice, device.scanForGames())
#tableData is a list of RetroManagerDatabase rmGame Objects
def reloadTable(self, tab, tableData):
print(f"Reloading Table {tab} with {len(tableData)} items")
#Disable cell change tracking to avoid infinite looping
tab.tbl_gamelist.itemChanged.disconnect()
tab.gameslist = tableData
tab.tbl_gamelist.setRowCount(len(tableData))
for i,game in enumerate(tableData):
#Filename
cell_filename = QTableWidgetItem(f"{game.title}")
cell_filename.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
tab.tbl_gamelist.setItem(i,0,cell_filename) #Filename
tab.tbl_gamelist.setItem(i,1,QTableWidgetItem(f"{game.console}")) #Console
tab.tbl_gamelist.setItem(i,2,QTableWidgetItem(f"{game.rating}")) #Rating
tab.tbl_gamelist.setItem(i,3,QTableWidgetItem(f"{game.series}")) #Series
tab.tbl_gamelist.setItem(i,4,QTableWidgetItem(f"{game.publisher}")) #Publisher
#Restore cell change tracking
tab.tbl_gamelist.itemChanged.connect(self.catchTableItemChanged)
def loadAllGamesToLibraryTable(self):
self.tab_library.gameslist = RMCore.rmdb.fetchGames()
print(f"gameslist contains {len(self.tab_library.gameslist)}")
self.reloadTable(self.tab_library, self.tab_library.gameslist)
def importGames(self):
try:
#Use the multi file select dialog to allow bulk importing
file_paths, _ = QFileDialog.getOpenFileNames(self, 'Select one or more games to import', '',"All Files (*.*)")
#Pass the games to the Core to handle parsing and storage
RMCore.importGames(file_paths)
self.loadAllGamesToLibraryTable()
except Exception as e:
print (f"{str(e)}")
return False
def catchTableItemChanged(self, item):
#TODO: update this function to make sure the change is made to the correct tab games list. Could use active tab?
#but that would mean that we can never make changes to a background tab
print(f"Cell changed ({item.row()},{item.column()})")
print(f"Changing linked game ({self.tab_library.gameslist[item.row()].title})")
columnType = self.library_columns[item.column()]
if columnType == self.library_columns_Title:
self.tab_library.gameslist[item.row()].title = item.text()
RMCore.rmdb.updateGame(self.tab_library.gameslist[item.row()])
elif columnType == self.library_columns_Console:
self.tab_library.gameslist[item.row()].console = item.text()
RMCore.rmdb.updateGame(self.tab_library.gameslist[item.row()])
def checkForNewDevices(self):
#Get the list of drives and compare it to the past history
for drive in psutil.disk_partitions():
if drive not in self.driveHistory:
if self.checkIfDeviceIsRetroGames(drive.mountpoint):
print(f"RetroManager~checkForNewDevices: New drive detected {drive.mountpoint}")
window.status_bar.showMessage(f"New Drive Detected - {drive.mountpoint}", 20000)
self.openDevice(drive.mountpoint)
self.driveHistory = psutil.disk_partitions()
#TODO: Should probably check that all the open devices are still actually connected.
def checkIfDeviceIsRetroGames(self, drive):
print(f"RetroManager~checkIfDeviceIsRetroGames: Checking if drive is game device: {drive}")
return RetroManagerDevice.isRetroManagerDrive(drive)
# Initialise logging
print(f"Setting logging path to ({static_LoggingPath})")
logging.basicConfig(filename=static_LoggingPath,
filemode='a',
format='%(asctime)s,%(msecs)d %(name)s %(levelname)s %(message)s',
datefmt='%H:%M:%S',
level=logging.DEBUG)
logging.info("RetroManager Started")
#Initialise the Application
app = QApplication(sys.argv)
# Build the Window
window = MainWindow()
window.show()
#window.driveHistory = psutil.disk_partitions()
window.driveHistory = []
# Listen for Devices Time
timer = QTimer()
timer.timeout.connect(window.checkForNewDevices)
timer.start(1000)
app.exec()