diff --git a/build.bat b/build.bat index c893736..6a23978 100644 --- a/build.bat +++ b/build.bat @@ -1,5 +1,5 @@ @echo off -set ver=0.3.1 +set ver=0.3.2 rem build script for the distributable versions of tadpole if not exist "venv\" ( py -m venv venv diff --git a/tadpole.py b/tadpole.py index e226e33..f7b4ae4 100644 --- a/tadpole.py +++ b/tadpole.py @@ -116,12 +116,12 @@ def loadROMsToTable(): def catchTableCellClicked(clickedRow, clickedColumn): print(f"clicked view thumbnail for {clickedRow},{clickedColumn}") - if clickedColumn == 2: + if clickedColumn == 2: #view thumbnail drive = window.combobox_drive.currentText() system = window.combobox_console.currentText() gamename = window.tbl_gamelist.item(clickedRow, 0).text() viewThumbnail(os.path.join(drive, system, gamename)) - elif clickedColumn == 3: + elif clickedColumn == 3: #change thumbnail drive = window.combobox_drive.currentText() system = window.combobox_console.currentText() gamename = window.tbl_gamelist.item(clickedRow, 0).text() @@ -143,9 +143,21 @@ def catchTableCellClicked(clickedRow, clickedColumn): def viewThumbnail(rom_path): - window.window_thumbnail = thumbnailWindow() - window.window_thumbnail.loadThumbnail(rom_path) - window.window_thumbnail.show() + window.window_thumbnail = thumbnailWindow(rom_path) + result = window.window_thumbnail.exec() + if result: + newLogoFileName = window.window_thumbnail.new_viewer.path + print(f"user tried to load image: {newLogoFileName}") + if newLogoFileName is None or newLogoFileName == "": + print("user cancelled image select") + return + + try: + tadpole_functions.changeZXXThumbnail(rom_path, newLogoFileName) + except tadpole_functions.Exception_InvalidPath: + QMessageBox.about(window, "Change ROM Cover", "An error occurred.") + return + QMessageBox.about(window, "Change ROM Logo", "ROM cover successfully changed") def BGM_change(source=""): # Check the selected drive looks like a Frog card @@ -647,20 +659,145 @@ def UpdatetoV1_5(self): # Subclass Qidget to create a thumbnail viewing window -class thumbnailWindow(QWidget): +class thumbnailWindow(QDialog): """ This window should be called without a parent widget so that it is created in its own window. """ - def __init__(self): + def __init__(self, filepath): super().__init__() layout = QVBoxLayout() - self.thumbnail = QLabel() - layout.addWidget(self.thumbnail) - self.setLayout(layout) - - def loadThumbnail(self, filepath): + + self.setWindowIcon(QIcon(self.style().standardIcon(QStyle.StandardPixmap.SP_DesktopIcon))) self.setWindowTitle(f"Thumbnail - {filepath}") - # self.thumbnail.setPixmap(QPixmap.fromImage(tadpole_functions.getThumbnailFromZXX(filepath))) + + # Setup Main Layout + self.layout_main = QVBoxLayout() + self.setLayout(self.layout_main) + + # set up current image viewer + self.layout_main.addWidget(QLabel("Current Image")) + self.current_viewer = ROMCoverViewer(self) + self.layout_main.addWidget(self.current_viewer, Qt.AlignCenter) + + self.layout_main.addWidget(QLabel(" ")) # spacer + + # set up new image viewer + self.layout_main.addWidget(QLabel("New Image")) + self.new_viewer = ROMCoverViewer(self, changeable=True) + self.layout_main.addWidget(self.new_viewer, Qt.AlignCenter) + + # Main Buttons Layout (Save/Cancel) + self.layout_buttons = QHBoxLayout() + self.layout_main.addLayout(self.layout_buttons) + + #Save Existing Cover To File Button + self.button_write = QPushButton("Save Existing to File") + self.button_write.clicked.connect(self.WriteImgToFile) + self.layout_buttons.addWidget(self.button_write) + + # Save Button + self.button_save = QPushButton("Overwrite Cover") + self.button_save.setDefault(True) + self.button_save.setDisabled(True) # set disabled by default; need to wait for user to select new image + self.button_save.clicked.connect(self.accept) + self.layout_buttons.addWidget(self.button_save) + + # Cancel Button + self.button_cancel = QPushButton("Cancel") + self.button_cancel.clicked.connect(self.reject) + self.layout_buttons.addWidget(self.button_cancel) + + # Load Initial Image + self.current_viewer.load_from_ROM(filepath) + + def WriteImgToFile(self): + newCoverFileName = QFileDialog.getSaveFileName(window, + 'Save Cover', + 'c:\\', + "Image files (*.png)")[0] + + if newCoverFileName is None or newCoverFileName == "": + print("user cancelled save select") + return + try: + tadpole_functions.extractImgFromROM(self.current_viewer.path, newCoverFileName) + except tadpole_functions.Exception_InvalidPath: + QMessageBox.about(window, "Save ROM Cover", "An error occurred.") + return + QMessageBox.about(window, "Save ROM Cover", "ROM cover successfully") + + + +class ROMCoverViewer(QLabel): + """ + Args: + parent (thumbnailWindow): Parent widget. Used to enable/disable controls on parent. + changeable (bool): If True, will allow importing new image. If False, will just allow static display. + """ + def __init__(self, parent, changeable=False): + super().__init__(parent) + + self.changeable = changeable + self.path = "" # Used to store path to the currently-displayed file + + self.setStyleSheet("background-color: white;") + self.setMinimumSize(144, 208) # resize to Froggy ROM logo dimensions + self.setFixedSize(144, 208) # resize to Froggy ROM logo dimensions + + if self.changeable: + self.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) + self.setText("Click to Select New Image") + + def mousePressEvent(self, ev): + """ + Overrides built-in function to handle mouse click events. Prompts user for image path and loads same. + """ + if self.changeable: # only do something if image is changeable + file_name = QFileDialog.getOpenFileName(self, 'Open file', '', + "Images (*.jpg *.png *.webp);;RAW (RGB565 Little Endian) Images (*.raw)")[0] + if len(file_name) > 0: # confirm if user selected a file + self.load_image(file_name) + + def load_from_ROM(self, pathToROM: str): + """ + Extracts image from the bios and passes to load image function. + + Args: + drive (str): Path to the root of the Froggy drive. + """ + print(f"loading cover from {pathToROM}") + with open(pathToROM, "rb") as rom_file: + rom_content = bytearray(rom_file.read()) + with open(os.path.join(basedir, "temp_rom_cover.raw"), "wb") as image_file: + image_file.write(rom_content[0:((144*208)*2)]) + + self.load_image(os.path.join(basedir, "temp_rom_cover.raw")) + + def load_image(self, path: str) -> bool: + """ + Loads an image into the viewer. If the image is loaded successfully, may enable the parent Save button based + on the changeable flag. + + Args: + path (str): Path to the image. Can be .raw or other format. If .raw, assumed to be in RGB16 (RGB565 Little + Endian) format used for Froggy boot logos. Must be 512x200 pixels or it will not be accepted/displayed. + + Returns: + bool: True if image was loaded, False if not. + """ + if os.path.splitext(path)[1] == ".raw": # if raw image, assume RGB16 (RGB565 Little Endian) + with open(path, "rb") as f: + img = QImage(f.read(), 144, 208, QImage.Format_RGB16) + else: # otherwise let QImage autodetection do its thing + img = QImage(path) + if (img.width(), img.height()) != (144, 208): + img = img.scaled(144, 208, Qt.IgnoreAspectRatio, Qt.SmoothTransformation) #Rescale new boot logo to correct size + self.path = path # update path + self.setPixmap(QPixmap().fromImage(img)) + + if self.changeable: # only enable saving for changeable dialogs; prevents enabling with load from bios + self.parent().button_save.setDisabled(False) + return True # Subclass Qidget to create a change shortcut window diff --git a/tadpole_functions.py b/tadpole_functions.py index c2a11cb..35fcfd4 100644 --- a/tadpole_functions.py +++ b/tadpole_functions.py @@ -3,7 +3,6 @@ import shutil import hashlib import zipfile -import io from PyQt5.QtGui import * from PyQt5.QtCore import * import struct @@ -129,10 +128,10 @@ def QImageToRGB565Logo(inputQImage): def getThumbnailFromZXX(filepath): """ file_handle = open(filepath, 'rb') #rb for read bytes - zxx_content = bytearray(file_handle.read(os.path.getsize(filepath)))#can probably reduce this to 288*104*2 + zxx_content = bytearray(file_handle.read(os.path.getsize(filepath)))#can probably reduce this to 208*104*2 file_handle.close() thumbnailQImage = QImage() - for y in range (0, 288): + for y in range (0, 208): for x in range (0, 104): #TODO intColor = @@ -439,3 +438,9 @@ def downloadFileFromGithub(outFile, url): with open(outFile, 'wb') as f: print(f'downloading {url} to {outFile}') f.write(r.content) + +def extractImgFromROM(romFilePath, outfilePath): + with open(romFilePath, "rb") as rom_file: + rom_content = bytearray(rom_file.read()) + img = QImage(rom_content[0:((144*208)*2)], 144, 208, QImage.Format_RGB16) + img.save(outfilePath) \ No newline at end of file diff --git a/versioninfo b/versioninfo index b3dada6..acf2076 100644 --- a/versioninfo +++ b/versioninfo @@ -1,7 +1,7 @@ VSVersionInfo( ffi=FixedFileInfo( - filevers=(0, 3, 1, 0), - prodvers=(0, 3, 1, 0), + filevers=(0, 3, 2, 0), + prodvers=(0, 3, 2, 0), mask=0x3f, flags=0x0, OS=0x4, @@ -15,12 +15,12 @@ VSVersionInfo( u'080904B0', [StringStruct(u'CompanyName', u'EricGoldstein'), StringStruct(u'FileDescription', u'tadpole'), - StringStruct(u'FileVersion', u'0.3.1'), + StringStruct(u'FileVersion', u'0.3.2'), StringStruct(u'InternalName', u'tadpole'), StringStruct(u'LegalCopyright', u'by EricGoldstein 2023'), StringStruct(u'OriginalFilename', u'tadpole.exe'), StringStruct(u'ProductName', u'tadpole'), - StringStruct(u'ProductVersion', u'0.3.1')]) + StringStruct(u'ProductVersion', u'0.3.2')]) ]), VarFileInfo([VarStruct(u'Translation', [2057, 1200])]) ]