ViewVC Help
View File | Revision Log | Show Annotations | View Changeset | Root Listing
root/ns_dev/Python/NinoCode/Active_prgs/Gromulus/Gromulus_UI.py
Revision: 987
Committed: Sun Mar 8 04:11:11 2026 UTC (2 weeks, 6 days ago) by nino.borges
Content type: text/x-python
File size: 24296 byte(s)
Log Message:
UI: enlarge artwork previews, preserve image aspect ratio, add click-to-open full-size artwork modal

- Increased preview widget size by ~5% for Box/Title/Ingame artwork.
- Fixed preview rendering to scale images proportionally and center them (no horizontal stretching).
- Added clickable artwork previews with hand cursor.
- Added modal full-size artwork viewer with scrollbars and close button.
- Added graceful handling for missing/unreadable artwork files.

File Contents

# User Rev Content
1 nino.borges 795 """
2     Created by Emanuel Borges
3     05.19.2023
4    
5     This is the main UI for Gromulus, which is a catalog inventory for roms, games and maybe one day applications.
6     Like most of my GUI programs, this will import a separate library with my methods for doing the actual work.
7    
8     """
9    
10    
11     import sys, os, wx
12     #import wx.lib.buttons as buttons
13 nino.borges 979 import wx.lib.agw.pybusyinfo as PBI
14 nino.borges 795 import Gromulus_Lib
15    
16    
17    
18     class MyFrame(wx.Frame):
19     def __init__(self, parent, ID, title, pos=wx.DefaultPosition):
20 nino.borges 979 wx.Frame.__init__(self, parent, ID, title, pos, size = (1450,925))
21 nino.borges 909 self.db = Gromulus_Lib.DatabaseManager()
22 nino.borges 976 self.dbConnection = self.db.get_connection()
23 nino.borges 795 self.panel = wx.Panel(self,-1)
24 nino.borges 976 self.currentSystemKey = "SNES"
25     self.gamesListMatrix = Gromulus_Lib.GetGameListBySystem(self.currentSystemKey, self.dbConnection)
26 nino.borges 795 gamesList = list(self.gamesListMatrix.keys())
27     gamesList.sort()
28 nino.borges 806 self.gamesCount = len(gamesList)
29     unmatchedGamesList=[i for i in gamesList if "." in i]
30     self.unmatchedGamesCount = len(unmatchedGamesList)
31     print(f"{self.gamesCount} total games for this system. {self.gamesCount - self.unmatchedGamesCount} matched and {self.unmatchedGamesCount} unmatched.")
32 nino.borges 795
33     self.CreateSystemButtonSection()
34 nino.borges 979 self.gameSelectionListBox = wx.ListBox(self.panel, 60, (100, 50), (490, 500), gamesList , wx.LB_SINGLE|wx.LB_OWNERDRAW)
35 nino.borges 795 self.CreateFieldsFirstRow()
36 nino.borges 979 self.CreateArtworkPreviewSection()
37 nino.borges 795
38    
39    
40     mainSizer = wx.BoxSizer(wx.HORIZONTAL)
41     mainSizer.Add(self.buttonSizer, 0, wx.ALIGN_TOP|wx.LEFT|wx.TOP,25)
42     mainSizer.Add(self.gameSelectionListBox, 0, wx.ALIGN_TOP|wx.LEFT|wx.TOP,25)
43     mainSizer.Add(self.fieldsFirstRowSizer, 0, wx.ALIGN_TOP|wx.LEFT|wx.TOP,25)
44    
45 nino.borges 979 rootSizer = wx.BoxSizer(wx.VERTICAL)
46     rootSizer.Add(mainSizer, 0, wx.EXPAND)
47     rootSizer.AddStretchSpacer(1)
48     rootSizer.Add(self.artworkPreviewSizer, 0, wx.ALIGN_CENTER|wx.ALL, 15)
49     self.panel.SetSizer(rootSizer)
50 nino.borges 795
51 nino.borges 979
52 nino.borges 795 self.CreateStatusBar()
53     self.SetStatusText("Ready.")
54     self.CreateMenuBar()
55    
56     self.Bind(wx. EVT_TOGGLEBUTTON, self.OnSystemSelected, self.snesSystemButton)
57     self.Bind(wx. EVT_TOGGLEBUTTON, self.OnSystemSelected, self.nesSystemButton)
58     self.Bind(wx. EVT_TOGGLEBUTTON, self.OnSystemSelected, self.genesisSystemButton)
59     self.Bind(wx. EVT_TOGGLEBUTTON, self.OnSystemSelected, self.fz1SystemButton)
60     self.Bind(wx. EVT_TOGGLEBUTTON, self.OnSystemSelected, self.ps1SystemButton)
61    
62     self.Bind(wx.EVT_LISTBOX, self.OnGameSelected, self.gameSelectionListBox)
63    
64 nino.borges 976 def _set_text(self, text_ctrl, value):
65     text_ctrl.SetValue(value if value else "")
66 nino.borges 795
67 nino.borges 979 def _load_scaled_bitmap(self, image_path, width, height):
68     if not image_path or not os.path.isfile(image_path):
69     return wx.Bitmap(width, height)
70     image = wx.Image(image_path, wx.BITMAP_TYPE_ANY)
71     if not image.IsOk():
72     return wx.Bitmap(width, height)
73    
74 nino.borges 987 src_w, src_h = image.GetSize()
75     if src_w <= 0 or src_h <= 0:
76     return wx.Bitmap(width, height)
77    
78     # Preserve aspect ratio and letterbox inside the preview area.
79     scale = min(width / src_w, height / src_h)
80     dst_w = max(1, int(src_w * scale))
81     dst_h = max(1, int(src_h * scale))
82     scaled_image = image.Scale(dst_w, dst_h, wx.IMAGE_QUALITY_HIGH)
83    
84     canvas = wx.Bitmap(width, height)
85     dc = wx.MemoryDC(canvas)
86     dc.SetBackground(wx.Brush(self.panel.GetBackgroundColour()))
87     dc.Clear()
88     x = (width - dst_w) // 2
89     y = (height - dst_h) // 2
90     dc.DrawBitmap(wx.Bitmap(scaled_image), x, y, True)
91     dc.SelectObject(wx.NullBitmap)
92     return canvas
93    
94 nino.borges 979 def _set_artwork_preview(self, bitmap_ctrl, image_path):
95 nino.borges 987 self.previewImagePathByCtrl[bitmap_ctrl] = image_path if image_path and os.path.isfile(image_path) else None
96 nino.borges 979 width, height = bitmap_ctrl.GetSize()
97     bitmap_ctrl.SetBitmap(self._load_scaled_bitmap(image_path, width, height))
98    
99     def _clear_artwork_previews(self):
100     self._set_artwork_preview(self.boxArtBitmap, None)
101     self._set_artwork_preview(self.titleArtBitmap, None)
102     self._set_artwork_preview(self.ingameArtBitmap, None)
103    
104 nino.borges 987 def _show_full_image_modal(self, title, image_path):
105     if not image_path or not os.path.isfile(image_path):
106     wx.MessageBox("No artwork file is available for this preview.", "Artwork Preview", wx.OK | wx.ICON_INFORMATION)
107     return
108    
109     image = wx.Image(image_path, wx.BITMAP_TYPE_ANY)
110     if not image.IsOk():
111     wx.MessageBox("Unable to load selected artwork file.", "Artwork Preview", wx.OK | wx.ICON_ERROR)
112     return
113    
114     bmp = wx.Bitmap(image)
115     img_w, img_h = bmp.GetSize()
116     display_w, display_h = wx.GetDisplaySize()
117     dialog_w = min(max(640, img_w + 60), int(display_w * 0.9))
118     dialog_h = min(max(480, img_h + 120), int(display_h * 0.9))
119    
120     dlg = wx.Dialog(self, title=title, size=(dialog_w, dialog_h), style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
121     panel = wx.Panel(dlg)
122     sizer = wx.BoxSizer(wx.VERTICAL)
123    
124     scroller = wx.ScrolledWindow(panel, style=wx.HSCROLL | wx.VSCROLL)
125     scroller.SetScrollRate(10, 10)
126     wx.StaticBitmap(scroller, -1, bmp, pos=(0, 0))
127     scroller.SetVirtualSize((img_w, img_h))
128    
129     close_btn = wx.Button(panel, wx.ID_OK, "Close")
130     sizer.Add(scroller, 1, wx.EXPAND | wx.ALL, 8)
131     sizer.Add(close_btn, 0, wx.ALIGN_RIGHT | wx.RIGHT | wx.BOTTOM, 8)
132     panel.SetSizer(sizer)
133    
134     dlg.ShowModal()
135     dlg.Destroy()
136    
137     def OnArtworkPreviewClicked(self, evt):
138     ctrl = evt.GetEventObject()
139     image_path = self.previewImagePathByCtrl.get(ctrl)
140     title = self.previewTitleByCtrl.get(ctrl, "Artwork Preview")
141     self._show_full_image_modal(title, image_path)
142    
143 nino.borges 979 def _run_with_busy(self, message, func, *args, **kwargs):
144     busy = PBI.PyBusyInfo(message, parent=self, title="System Busy: Please Wait.")
145     wx.YieldIfNeeded()
146     try:
147     return func(*args, **kwargs)
148     finally:
149     del busy
150    
151 nino.borges 976 def _load_games_for_system(self, system_key):
152     self.currentSystemKey = system_key
153     self.gamesListMatrix = Gromulus_Lib.GetGameListBySystem(system_key, self.dbConnection)
154     gamesList = list(self.gamesListMatrix.keys())
155     gamesList.sort()
156    
157     self.gameSelectionListBox.Clear()
158     self.gameSelectionListBox.AppendItems(gamesList)
159    
160     self._set_text(self.gameNameTextCtrl, "")
161     self._set_text(self.gameHashTextCtrl, "")
162     self._set_text(self.gameFileNameTextCtrl, "")
163     self._set_text(self.gameFilePathTextCtrl, "")
164     self._set_text(self.noIntroGameNameTextCtrl, "")
165     self._set_text(self.noIntroSystemNameTextCtrl, "")
166 nino.borges 978 self._set_text(self.tosecGameNameTextCtrl, "")
167 nino.borges 979 self._clear_artwork_previews()
168 nino.borges 976
169 nino.borges 979 def _choose_system(self, prompt_title, only_with_roms=False):
170     systemMatrix = (
171     Gromulus_Lib.GetSystemListWithRoms(self.dbConnection)
172     if only_with_roms
173     else Gromulus_Lib.GetSystemList(self.dbConnection)
174     )
175 nino.borges 978 systemsList = list(systemMatrix.keys())
176     if not systemsList:
177 nino.borges 979 wx.MessageBox("No eligible systems found.", "System Selection", wx.OK | wx.ICON_WARNING)
178 nino.borges 978 return None
179 nino.borges 976
180 nino.borges 979 dlg = wx.SingleChoiceDialog(self, "Select system", prompt_title, systemsList)
181 nino.borges 978 if dlg.ShowModal() != wx.ID_OK:
182     return None
183     selected_name = dlg.GetStringSelection()
184     return systemMatrix[selected_name][0]
185    
186    
187 nino.borges 795 def CreateSystemButtonSection(self):
188     #systemsList = ['SNES','NES','Genisys']
189     #for system in systemsList:
190     #self.snesSystemButton = buttons.GenToggleButton(self.panel, -1, "SNES")
191     #self.nesSystemButton = buttons.GenToggleButton(self.panel, -1, "NES")
192     #self.genesisSystemButton = buttons.GenToggleButton(self.panel, -1, "Genesis")
193     #self.ps1SystemButton = buttons.GenToggleButton(self.panel, -1, "Playstation")
194    
195     ## Create a dictionary that holds the button instances by label text, so that you can toggle the other ones off in the bind event.
196     self.systemButtonDict = {}
197 nino.borges 976 self.buttonSystemMap = {}
198 nino.borges 795 self.snesSystemButton = wx.ToggleButton(self.panel, -1, "SNES")
199     self.systemButtonDict[self.snesSystemButton.GetLabelText()] = self.snesSystemButton
200 nino.borges 976 self.buttonSystemMap[self.snesSystemButton.GetLabelText()] = "SNES"
201 nino.borges 795
202     self.nesSystemButton = wx.ToggleButton(self.panel, -1, "NES")
203     self.systemButtonDict[self.nesSystemButton.GetLabelText()] = self.nesSystemButton
204 nino.borges 976 self.buttonSystemMap[self.nesSystemButton.GetLabelText()] = "NES"
205 nino.borges 795
206     self.genesisSystemButton = wx.ToggleButton(self.panel, -1, "Genesis")
207     self.systemButtonDict[self.genesisSystemButton.GetLabelText()] = self.genesisSystemButton
208 nino.borges 976 self.buttonSystemMap[self.genesisSystemButton.GetLabelText()] = "Genesis"
209 nino.borges 795
210     self.fz1SystemButton = wx.ToggleButton(self.panel, -1, "3DO")
211     self.systemButtonDict[self.fz1SystemButton.GetLabelText()] = self.fz1SystemButton
212 nino.borges 976 self.buttonSystemMap[self.fz1SystemButton.GetLabelText()] = "3DO"
213 nino.borges 795
214     self.ps1SystemButton = wx.ToggleButton(self.panel, -1, "Playstation")
215     self.systemButtonDict[self.ps1SystemButton.GetLabelText()] = self.ps1SystemButton
216 nino.borges 976 self.buttonSystemMap[self.ps1SystemButton.GetLabelText()] = "Playstation"
217     self.snesSystemButton.SetValue(True)
218 nino.borges 795
219    
220     self.buttonSizer = wx.BoxSizer(wx.VERTICAL)
221     self.buttonSizer.Add(self.snesSystemButton, 0, wx.ALL,10)
222     self.buttonSizer.Add(self.nesSystemButton,0,wx.ALL,10)
223     self.buttonSizer.Add(self.genesisSystemButton,0,wx.ALL,10)
224     self.buttonSizer.Add(self.fz1SystemButton,0,wx.ALL,10)
225     self.buttonSizer.Add(self.ps1SystemButton,0,wx.ALL,10)
226    
227     def CreateFieldsFirstRow(self):
228     self.gameNameStaticText = wx.StaticText(self.panel, -1, "Game Name:")
229     self.gameNameTextCtrl = wx.TextCtrl(self.panel, -1, size=(325, -1))
230    
231     self.gameHashStaticText = wx.StaticText(self.panel, -1, "Game Hash:")
232     self.gameHashTextCtrl = wx.TextCtrl(self.panel, -1, size=(325, -1))
233    
234     self.gameFileNameStaticText = wx.StaticText(self.panel, -1, "Game File Name:")
235     self.gameFileNameTextCtrl = wx.TextCtrl(self.panel, -1, size=(325, -1))
236    
237     self.gameFilePathStaticText = wx.StaticText(self.panel, -1, "Game File Path:")
238     self.gameFilePathTextCtrl = wx.TextCtrl(self.panel, -1, size=(425, -1))
239    
240     self.noIntroGameNameStaticText = wx.StaticText(self.panel, -1, "No Intro Game Name:")
241     self.noIntroGameNameTextCtrl = wx.TextCtrl(self.panel, -1, size=(325, -1))
242    
243     self.noIntroSystemNameStaticText = wx.StaticText(self.panel, -1, "No Intro System:")
244     self.noIntroSystemNameTextCtrl = wx.TextCtrl(self.panel, -1, size=(380, -1))
245    
246 nino.borges 976 self.tosecGameNameStaticText = wx.StaticText(self.panel, -1, "TOSEC Game Name:")
247     self.tosecGameNameTextCtrl = wx.TextCtrl(self.panel, -1, size=(325, -1))
248    
249 nino.borges 795 self.gameNameSizer = wx.BoxSizer(wx.HORIZONTAL)
250     self.gameNameSizer.Add(self.gameNameStaticText,0,wx.ALL, 10)
251     self.gameNameSizer.Add(self.gameNameTextCtrl,0,wx.ALL, 10)
252    
253     self.gameHashSizer = wx.BoxSizer(wx.HORIZONTAL)
254     self.gameHashSizer.Add(self.gameHashStaticText,0,wx.ALL, 10)
255     self.gameHashSizer.Add(self.gameHashTextCtrl,0,wx.ALL, 10)
256    
257     self.gameFileNameSizer = wx.BoxSizer(wx.HORIZONTAL)
258     self.gameFileNameSizer.Add(self.gameFileNameStaticText,0,wx.ALL, 10)
259     self.gameFileNameSizer.Add(self.gameFileNameTextCtrl,0,wx.ALL, 10)
260    
261     self.gameFilePathSizer = wx.BoxSizer(wx.HORIZONTAL)
262     self.gameFilePathSizer.Add(self.gameFilePathStaticText,0,wx.ALL, 10)
263     self.gameFilePathSizer.Add(self.gameFilePathTextCtrl,0,wx.ALL, 10)
264    
265     self.noIntroGameNameSizer = wx.BoxSizer(wx.HORIZONTAL)
266     self.noIntroGameNameSizer.Add(self.noIntroGameNameStaticText,0,wx.ALL, 10)
267     self.noIntroGameNameSizer.Add(self.noIntroGameNameTextCtrl,0,wx.ALL, 10)
268    
269     self.noIntroSystemNameSizer = wx.BoxSizer(wx.HORIZONTAL)
270     self.noIntroSystemNameSizer.Add(self.noIntroSystemNameStaticText,0,wx.ALL, 10)
271     self.noIntroSystemNameSizer.Add(self.noIntroSystemNameTextCtrl,0,wx.ALL, 10)
272    
273 nino.borges 976 self.tosecGameNameSizer = wx.BoxSizer(wx.HORIZONTAL)
274     self.tosecGameNameSizer.Add(self.tosecGameNameStaticText,0,wx.ALL, 10)
275     self.tosecGameNameSizer.Add(self.tosecGameNameTextCtrl,0,wx.ALL, 10)
276    
277 nino.borges 795 self.fieldsFirstRowSizer = wx.BoxSizer(wx.VERTICAL)
278     self.fieldsFirstRowSizer.Add(self.gameNameSizer,0,wx.ALL, 10)
279     self.fieldsFirstRowSizer.Add(self.gameHashSizer,0,wx.ALL, 10)
280     self.fieldsFirstRowSizer.Add(self.gameFileNameSizer,0,wx.ALL, 10)
281     self.fieldsFirstRowSizer.Add(self.gameFilePathSizer,0,wx.ALL, 10)
282     self.fieldsFirstRowSizer.Add(self.noIntroGameNameSizer,0,wx.ALL, 10)
283     self.fieldsFirstRowSizer.Add(self.noIntroSystemNameSizer,0,wx.ALL, 10)
284 nino.borges 976 self.fieldsFirstRowSizer.Add(self.tosecGameNameSizer,0,wx.ALL, 10)
285 nino.borges 795
286 nino.borges 979 def CreateArtworkPreviewSection(self):
287 nino.borges 987 preview_size = (315, 189)
288 nino.borges 795
289 nino.borges 979 boxSizer = wx.StaticBoxSizer(wx.VERTICAL, self.panel, "Box Art")
290     self.boxArtBitmap = wx.StaticBitmap(self.panel, -1, wx.Bitmap(*preview_size), size=preview_size)
291     boxSizer.Add(self.boxArtBitmap, 0, wx.ALL, 8)
292    
293     titleSizer = wx.StaticBoxSizer(wx.VERTICAL, self.panel, "Title Art")
294     self.titleArtBitmap = wx.StaticBitmap(self.panel, -1, wx.Bitmap(*preview_size), size=preview_size)
295     titleSizer.Add(self.titleArtBitmap, 0, wx.ALL, 8)
296    
297     ingameSizer = wx.StaticBoxSizer(wx.VERTICAL, self.panel, "Ingame Art")
298     self.ingameArtBitmap = wx.StaticBitmap(self.panel, -1, wx.Bitmap(*preview_size), size=preview_size)
299     ingameSizer.Add(self.ingameArtBitmap, 0, wx.ALL, 8)
300    
301     self.artworkPreviewSizer = wx.BoxSizer(wx.HORIZONTAL)
302     self.artworkPreviewSizer.Add(boxSizer, 0, wx.ALL, 10)
303     self.artworkPreviewSizer.Add(titleSizer, 0, wx.ALL, 10)
304     self.artworkPreviewSizer.Add(ingameSizer, 0, wx.ALL, 10)
305 nino.borges 987
306     self.previewImagePathByCtrl = {}
307     self.previewTitleByCtrl = {
308     self.boxArtBitmap: "Box Art Preview",
309     self.titleArtBitmap: "Title Art Preview",
310     self.ingameArtBitmap: "Ingame Art Preview",
311     }
312     self.boxArtBitmap.SetCursor(wx.Cursor(wx.CURSOR_HAND))
313     self.titleArtBitmap.SetCursor(wx.Cursor(wx.CURSOR_HAND))
314     self.ingameArtBitmap.SetCursor(wx.Cursor(wx.CURSOR_HAND))
315     self.boxArtBitmap.Bind(wx.EVT_LEFT_UP, self.OnArtworkPreviewClicked)
316     self.titleArtBitmap.Bind(wx.EVT_LEFT_UP, self.OnArtworkPreviewClicked)
317     self.ingameArtBitmap.Bind(wx.EVT_LEFT_UP, self.OnArtworkPreviewClicked)
318    
319 nino.borges 979 self._clear_artwork_previews()
320    
321    
322 nino.borges 795 def MenuData(self):
323     return(("&Tools",
324 nino.borges 805 ("&Add Roms","Adds new Roms to a selected system.", self.OnAddNewRoms),
325 nino.borges 979 ("Add &Artwork","Adds artwork files to matched games for a selected system.", self.OnAddNewArtwork),
326 nino.borges 805 ("Import &NoIntro DAT", "Allows for the import of any of the No Intro DAT files.", self.OnImportNewNoIntroDat),
327 nino.borges 806 ("Import &TOSEC DAT", "Allows for the import of any of the TOSEC DAT files.", self.OnImportNewTosecDat)),
328 nino.borges 795 ("&Reports",
329     ("&Duplicates Report","Generates a report detailing duplicates that exist in your collection.", self.NothingYet)),
330     ("&Help",
331     ("&About", "Displays the About Window", self.NothingYet)))
332    
333     def CreateMenuBar(self):
334     menuBar = wx.MenuBar()
335     for eachMenuData in self.MenuData():
336     menuLabel = eachMenuData[0]
337     menuItems = eachMenuData[1:]
338     menuBar.Append(self.CreateMenu(menuItems), menuLabel)
339     self.SetMenuBar(menuBar)
340    
341     def CreateMenu(self, menuData):
342     menu = wx.Menu()
343     for eachLabel, eachStatus, eachHandler in menuData:
344     if not eachLabel:
345     menu.AppendSeparator()
346     continue
347     menuItem = menu.Append(-1, eachLabel, eachStatus)
348     self.Bind(wx.EVT_MENU, eachHandler, menuItem)
349     return menu
350    
351     def OnSystemSelected(self, evt):
352 nino.borges 976 if not evt.GetEventObject().GetValue():
353     evt.GetEventObject().SetValue(True)
354     return
355    
356 nino.borges 795 systemsButtonNamesList = list(self.systemButtonDict.keys())
357     systemsButtonNamesList.remove(evt.GetEventObject().GetLabelText())
358     for i in systemsButtonNamesList:
359     self.systemButtonDict[i].SetValue(False)
360    
361 nino.borges 976 system_key = self.buttonSystemMap.get(evt.GetEventObject().GetLabelText(), "SNES")
362 nino.borges 979 self._run_with_busy("Loading games for selected system...", self._load_games_for_system, system_key)
363 nino.borges 795
364    
365     def OnGameSelected(self, evt):
366     print(self.gameSelectionListBox.GetStringSelection())
367 nino.borges 976 selected = self.gameSelectionListBox.GetStringSelection()
368     if not selected:
369     return
370 nino.borges 795
371 nino.borges 976 gameData = Gromulus_Lib.GetSingleGameById(self.gamesListMatrix[selected], self.dbConnection)
372     if not gameData:
373     return
374 nino.borges 795
375 nino.borges 976 display_name = gameData.get("game_name") or gameData.get("no_intro_game") or gameData.get("tosec_game") or gameData.get("filename")
376     no_intro_name = gameData.get("no_intro_game")
377     no_intro_system = gameData.get("no_intro_system") or gameData.get("tosec_system")
378     tosec_name = gameData.get("tosec_game")
379    
380     self._set_text(self.gameNameTextCtrl, display_name)
381     self._set_text(self.gameHashTextCtrl, gameData.get("hash"))
382     self._set_text(self.gameFileNameTextCtrl, gameData.get("filename"))
383     self._set_text(self.gameFilePathTextCtrl, gameData.get("path"))
384     self._set_text(self.noIntroGameNameTextCtrl, no_intro_name)
385     self._set_text(self.noIntroSystemNameTextCtrl, no_intro_system)
386     self._set_text(self.tosecGameNameTextCtrl, tosec_name)
387 nino.borges 979 self._set_artwork_preview(self.boxArtBitmap, gameData.get("box_art_path"))
388     self._set_artwork_preview(self.titleArtBitmap, gameData.get("title_art_path"))
389     self._set_artwork_preview(self.ingameArtBitmap, gameData.get("ingame_art_path"))
390 nino.borges 976
391 nino.borges 795 def NothingYet(self, event):
392     diag = wx.MessageDialog(self, "Nothing here yet!", "Disabled...", wx.OK | wx.ICON_INFORMATION)
393     diag.ShowModal()
394     diag.Destroy()
395    
396 nino.borges 805 def OnImportNewNoIntroDat(self,event):
397 nino.borges 979 selected_system_id = self._choose_system("No-Intro DAT System Selection")
398 nino.borges 978 if not selected_system_id:
399     return
400    
401 nino.borges 805 dlg = wx.FileDialog(
402     self, message="Import New NoIntro DAT ...", defaultDir=os.getcwd(),
403     defaultFile="", wildcard="DAT files (*.dat)|*.dat", style=wx.FD_OPEN | wx.FD_CHANGE_DIR
404     )
405     if dlg.ShowModal() == wx.ID_OK:
406     datPath = dlg.GetPath()
407 nino.borges 978 Gromulus_Lib.ImportNewNoIntroDat(datPath, selected_system_id, self.dbConnection)
408 nino.borges 805 doneDiag = wx.MessageDialog(self, "NoIntro DAT imported","All Done!",wx.OK | wx.ICON_INFORMATION)
409     doneDiag.ShowModal()
410     doneDiag.Destroy()
411 nino.borges 795
412 nino.borges 806 def OnImportNewTosecDat(self,event):
413 nino.borges 979 selected_system_id = self._choose_system("TOSEC DAT System Selection")
414 nino.borges 978 if not selected_system_id:
415     return
416    
417 nino.borges 806 dlg = wx.FileDialog(
418     self, message="Import New Tosec DAT ...", defaultDir=os.getcwd(),
419     defaultFile="", wildcard="DAT files (*.dat)|*.dat", style=wx.FD_OPEN | wx.FD_CHANGE_DIR
420     )
421     if dlg.ShowModal() == wx.ID_OK:
422     datPath = dlg.GetPath()
423 nino.borges 978 Gromulus_Lib.ImportNewTosecDat(datPath, selected_system_id, self.dbConnection)
424 nino.borges 806 doneDiag = wx.MessageDialog(self, "Tosec DAT imported","All Done!",wx.OK | wx.ICON_INFORMATION)
425     doneDiag.ShowModal()
426     doneDiag.Destroy()
427    
428 nino.borges 979 def OnAddNewArtwork(self, event):
429     selected_system_id = self._choose_system("Artwork System Selection", only_with_roms=True)
430     if not selected_system_id:
431     return
432 nino.borges 806
433 nino.borges 979 artwork_type_map = {
434     "Game Box Art": "box",
435     "Game Title": "title",
436     "Game Ingame Screenshot": "ingame",
437     }
438     type_choices = list(artwork_type_map.keys())
439     type_dlg = wx.SingleChoiceDialog(
440     self,
441     "Select the type of artwork to ingest",
442     "Artwork Type",
443     type_choices,
444     )
445     if type_dlg.ShowModal() != wx.ID_OK:
446     return
447     selected_artwork_label = type_dlg.GetStringSelection()
448     artwork_type_key = artwork_type_map[selected_artwork_label]
449    
450     dry_run_dlg = wx.MessageDialog(
451     self,
452     "Run as dry run (preview only, no file copy and no DB updates)?",
453     "Artwork Import Mode",
454     wx.YES_NO | wx.ICON_QUESTION,
455     )
456     dry_run_mode = dry_run_dlg.ShowModal() == wx.ID_YES
457     dry_run_dlg.Destroy()
458    
459     dir_dlg = wx.DirDialog(
460     self,
461     f"Choose a directory containing {selected_artwork_label} artwork files:",
462     style=wx.DD_DEFAULT_STYLE,
463     )
464     if dir_dlg.ShowModal() != wx.ID_OK:
465     return
466     artwork_source_dir = dir_dlg.GetPath()
467    
468     matched_count, skipped_existing_count, unmatched_count, error_count = self._run_with_busy(
469     "Matching artwork files and updating game records...",
470     Gromulus_Lib.AddNewArtwork,
471     artwork_source_dir,
472     selected_system_id,
473     artwork_type_key,
474     self.dbConnection,
475     testOnly=dry_run_mode,
476     )
477     doneDiag = wx.MessageDialog(
478     self,
479     (
480     f"Mode: {'Dry Run (no files written)' if dry_run_mode else 'Import'}\n\n"
481     f"{matched_count} artwork files matched and imported.\n"
482     f"{skipped_existing_count} games skipped (artwork already set).\n"
483     f"{unmatched_count} games had no filename/title match.\n"
484     f"{error_count} files failed during copy/update."
485     ),
486     "Artwork import complete",
487     wx.OK | wx.ICON_INFORMATION,
488     )
489     doneDiag.ShowModal()
490     doneDiag.Destroy()
491    
492    
493 nino.borges 805 def OnAddNewRoms(self,event):
494 nino.borges 908 systemMatrix = Gromulus_Lib.GetSystemList(self.dbConnection)
495 nino.borges 805 systemsList = list(systemMatrix.keys())
496     dlg = wx.SingleChoiceDialog(self,
497     "Select system for new ROMs",
498     "List of systemss", systemsList)
499     if (dlg.ShowModal() == wx.ID_OK):
500     dlg2 = wx.DirDialog(self, "Choose a directory:",
501     style=wx.DD_DEFAULT_STYLE)
502     if (dlg2.ShowModal() == wx.ID_OK):
503     romsToImportPath = dlg2.GetPath()
504     systemID = systemMatrix[dlg.GetStringSelection()][0]
505 nino.borges 908 importedCount, alreadyExistsCount, errImportCount = Gromulus_Lib.AddNewRoms(romsToImportPath, systemID, self.dbConnection)#, testOnly=True)
506 nino.borges 805 #print(systemMatrix[dlg.GetStringSelection()])
507     #print(dlg2.GetPath())
508     doneDiag = wx.MessageDialog(self, f"{str(importedCount)} files imported.\n{str(alreadyExistsCount)} files not imported because you already have them.\n{str(errImportCount)} files imported because of errors.", "Rom import process complete",wx.OK | wx.ICON_INFORMATION)
509     doneDiag.ShowModal()
510     doneDiag.Destroy()
511    
512    
513    
514 nino.borges 795 class MyApp(wx.App):
515     def OnInit(self):
516 nino.borges 979 self.frame = MyFrame(None, -1, "Gromulus v1.3")
517 nino.borges 795 self.frame.Show(True)
518     self.SetTopWindow(self.frame)
519     return True
520 nino.borges 908 def OnExit(self):
521 nino.borges 910 if hasattr(self, "frame") and hasattr(self.frame, "db"):
522     self.frame.db.close()
523 nino.borges 908 print("Closed DB")
524     return 0
525 nino.borges 795
526    
527     if __name__ == '__main__':
528     app = MyApp(0)
529 nino.borges 976 app.MainLoop()