diff --git a/README.md b/README.md new file mode 100644 index 0000000..e17d87a --- /dev/null +++ b/README.md @@ -0,0 +1,49 @@ +

+ logo +

+

+ An app to gather most of SCS games modding tools in one place +

+ +

+ GitHub Release + GitHub repo size + python + code style +

+ +## +

+ + +

+ +## Feature +- [x] [**Converter PIX**](https://forum.scssoft.com/viewtopic.php?t=216158) + - [x] Single Model and Single TOBJ mode with conversion + - [x] Animation export + - [x] Past 1.47 material attributes + - [x] Extract specific File and Folder without conversion + - [ ] Convert whole archive +- [x] [**SCS Extractor**](https://modding.scssoft.com/wiki/Documentation/Tools/Game_Archive_Extractor) + - [x] Extract whole archive without conversion +- [ ] [**SXC Extractor**](https://forum.scssoft.com/viewtopic.php?t=276948) +- [ ] [**Batch DDS to TGA**](https://forum.scssoft.com/viewtopic.php?t=190908) + +## Known issue +- App + - Not tested in Linux and Mac + - `Open log` and `Open Folder` buttons in InfoBar only work in windows + - `Badge` on button will be miss-aligned after select archive +- Converter PIX + - Ctrl + A in anim list will select all file and folder (just `Select & drag mouse` or `Single click`) +- SCS Extractor + - Only work in windows + +## Thanks to + +> [**zhiyiYo**](https://github.com/zhiyiYo) - [PyQt Fluent Widgets](https://github.com/zhiyiYo/PyQt-Fluent-Widgets) and it's gallery app code + +> [**mwl4**](https://github.com/mwl4) - [Converter PIX](https://github.com/mwl4/ConverterPIX) project + +> [**simon50keda**](https://github.com/simon50keda) - [Converter PIX Wrapper](https://github.com/simon50keda/ConverterPIXWrapper) for inspiration and code help \ No newline at end of file diff --git a/image/converter_pix.png b/image/converter_pix.png new file mode 100644 index 0000000..3a1cd2e Binary files /dev/null and b/image/converter_pix.png differ diff --git a/image/home.png b/image/home.png new file mode 100644 index 0000000..2c0a7c7 Binary files /dev/null and b/image/home.png differ diff --git a/scshub/common/tool.py b/scshub/common/tool.py index bda6608..82dbe07 100644 --- a/scshub/common/tool.py +++ b/scshub/common/tool.py @@ -23,8 +23,6 @@ SCSHUB_FEEDBACK_URL = "https://github.com/AmirMahdaviAM/SCSHub/issues" SCSHUB_FORUM_URL = "https://forum.scssoft.com" FORUM_URL = "https://forum.scssoft.com" -CONVERTERPIX_GITHUB_URL = "https://forum.scssoft.com" - # set tools path TOOLS_PATH = os.path.join(os.getcwd(), "tools") @@ -59,6 +57,7 @@ class ScsHubIcon(FluentIconBase, Enum): SCS_EXTRACTOR_FILL = "interface/scs_extractor_fill" SCS = "scs" + FILE = "file" FOLDER = "folder" ANIM = "anim" diff --git a/scshub/view/interface/home_interface.py b/scshub/view/interface/home_interface.py index 9bba08a..cf3dd6c 100644 --- a/scshub/view/interface/home_interface.py +++ b/scshub/view/interface/home_interface.py @@ -1,6 +1,6 @@ from PyQt5.QtCore import Qt, QRectF from PyQt5.QtGui import QPixmap, QPainter, QColor, QBrush, QPainterPath, QLinearGradient -from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QSizePolicy +from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel from qfluentwidgets import ScrollArea, FluentIcon, isDarkTheme @@ -37,31 +37,7 @@ def __init__(self, parent=None): ) self.linkCardView.addCard( FluentIcon.GITHUB, - self.tr("GitHub repo"), - self.tr("Repository with source code and details"), - SCSHUB_GITHUB_URL, - ) - self.linkCardView.addCard( - ScsHubIcon.SCS, - self.tr("SCS Forum"), - self.tr("Forum topic and all information about SCS Hub"), - SCSHUB_FORUM_URL, - ) - self.linkCardView.addCard( - FluentIcon.GITHUB, - self.tr("GitHub repo"), - self.tr("Repository with source code and details"), - SCSHUB_GITHUB_URL, - ) - self.linkCardView.addCard( - ScsHubIcon.SCS, - self.tr("SCS Forum"), - self.tr("Forum topic and all information about SCS Hub"), - SCSHUB_FORUM_URL, - ) - self.linkCardView.addCard( - FluentIcon.GITHUB, - self.tr("GitHub repo"), + self.tr("GitHub Repo"), self.tr("Repository with source code and details"), SCSHUB_GITHUB_URL, ) @@ -114,34 +90,32 @@ def __init__(self, parent=None): self.setWidgetResizable(True) self.banner = BannerWidget(self) - # self.mame = CreditWidget(self) self.vBoxLayout = QVBoxLayout(self.view) - self.vBoxLayout.setContentsMargins(0, 0, 0, 0) # left, top, right, down + self.vBoxLayout.setContentsMargins(0, 0, 0, 0) self.vBoxLayout.setSpacing(40) self.vBoxLayout.addWidget(self.banner) - # self.vBoxLayout.addWidget(self.mame) StyleSheet.HOME_INTERFACE.apply(self) appsView = InterfaceCardView(self.tr("Apps"), self.view) appsView.addSampleCard( - FluentIcon.DEVELOPER_TOOLS, - "Converter PIX", + ScsHubIcon.PIX_CONVERTER, + self.tr("Converter PIX"), self.tr("Extractor and converter (*.PMX to *.PIX)"), "pixInterface", ) appsView.addSampleCard( - FluentIcon.IMAGE_EXPORT, - "SCS Extractor", - self.tr("Extract whole *.scs archive file without any change"), + ScsHubIcon.SCS_EXTRACTOR, + self.tr("SCS Extractor"), + self.tr("Extract whole *.scs archive file without conversion"), "scsInterface", ) appsView.addSampleCard( FluentIcon.SETTING, - "Setting", - self.tr("A place to configure app and change theme"), + self.tr("Settings"), + self.tr("Place to configure and personlize app"), "settingInterface", ) diff --git a/scshub/view/interface/pix_interface.py b/scshub/view/interface/pix_interface.py index d0f2540..9c0e810 100644 --- a/scshub/view/interface/pix_interface.py +++ b/scshub/view/interface/pix_interface.py @@ -7,9 +7,19 @@ from PyQt5.QtCore import Qt, QProcess from PyQt5.QtWidgets import QWidget, QFileDialog, QListWidgetItem -from qfluentwidgets import (InfoBar, InfoBarPosition, InfoBarIcon, IndeterminateProgressRing, - PushButton, ToolTipFilter, FluentIcon, InfoLevel, FluentIconBase, - ListWidget, InfoBadge) +from qfluentwidgets import ( + InfoBar, + InfoBarPosition, + InfoBarIcon, + IndeterminateProgressRing, + PushButton, + ToolTipFilter, + FluentIcon, + InfoLevel, + FluentIconBase, + ListWidget, + InfoBadge, +) from scshub.view.ui.pix_ui import Ui_PIX from scshub.common.tool import ScsHubIcon, downloader, PIX_URL, PIX_PATH @@ -38,7 +48,7 @@ class PixInterface(QWidget, Ui_PIX): def __init__(self): super().__init__() - #check for log file and delete it + # check for log file and delete it if os.path.isfile("pix.log"): os.remove("pix.log") logger.info("Delete last pix.log file") @@ -48,7 +58,7 @@ def __init__(self): self.PIX_MODE = "" self.MODE = "-extract_f" - + self.SUB_PATH = "/" self.ANIM_PATH = "/" self.SUFFIX_END = "" @@ -99,13 +109,13 @@ def initUi(self): self.tobjRadio.installEventFilter(ToolTipFilter(self.tobjRadio)) self.extdirRadio.installEventFilter(ToolTipFilter(self.extdirRadio)) self.extfileRadio.installEventFilter(ToolTipFilter(self.extfileRadio)) - + self.materialCheckbox.stateChanged.connect(lambda: self.materialOption()) self.materialCheckbox.installEventFilter(ToolTipFilter(self.materialCheckbox)) self.animCheckBox.stateChanged.connect(lambda: self.animOption()) self.animCheckBox.installEventFilter(ToolTipFilter(self.animCheckBox)) - + self.animListCard.hide() self.animList.setSelectionMode(ListWidget.MultiSelection) self.animList.currentTextChanged.connect(self.indexAnimFolder) @@ -138,7 +148,6 @@ def initUi(self): self.runButton.clicked.connect(lambda: self.customMode()) - def listdirMode(self): """Run ConverterPIX to create directory and file lists""" @@ -149,7 +158,7 @@ def listdirMode(self): args = "" for file_path in self.SCS_FILES: args += f'{Argument.BASE} "{file_path}" ' - args += f'{Argument.LISTDIR} {self.SUB_PATH}' + args += f"{Argument.LISTDIR} {self.SUB_PATH}" logger.info(f"Change process to listdir mode with these argument: ({args})") @@ -167,23 +176,25 @@ def customMode(self): args += f'{Argument.BASE} "{file_path}" ' if self.MATERIAL147 and self.materialCheckbox.isEnabled(): - args += f'{Argument.NEW_MAT} ' - args += f'{self.MODE} ' + args += f"{Argument.NEW_MAT} " + args += f"{self.MODE} " if self.MODE == "-m": - args += f'{self.SUB_PATH[:-4]} ' + args += f"{self.SUB_PATH[:-4]} " else: - args += f'{self.SUB_PATH} ' + args += f"{self.SUB_PATH} " if self.ANIMMODE and self.animCheckBox.isEnabled() and self.SELECTED_ANIM_FILES != []: for file in self.SELECTED_ANIM_FILES: - anim = os.path.join(self.SUB_PATH.replace(self.LAST_SELECTED_FILE, ""), file[:-4]).replace("\\", "/") - args += f'{anim} ' + anim = os.path.join( + self.SUB_PATH.replace(self.LAST_SELECTED_FILE, ""), file[:-4] + ).replace("\\", "/") + args += f"{anim} " args += f'{Argument.EXP} "{self.EXPORT_PATH}"' logger.info(f"Change process to custom mode with these argument: ({args})") - + # run converter pix self.pixMainProcess(args) @@ -194,14 +205,13 @@ def animMode(self): args = "" for file_path in self.SCS_FILES: args += f'{Argument.BASE} "{file_path}" ' - args += f'{Argument.LISTDIR} {self.ANIM_PATH}' + args += f"{Argument.LISTDIR} {self.ANIM_PATH}" logger.info(f"Change process to anim mode with these argument: ({args})") # run converter pix self.pixAnimProcess(args) - def selectMode(self, mode): """Change argument with selected modes""" @@ -251,7 +261,7 @@ def materialOption(self): self.MATERIAL147 = True else: self.MATERIAL147 = False - + logger.info(f"Change past 1.47 material option state to {self.MATERIAL147}") def animOption(self): @@ -263,13 +273,12 @@ def animOption(self): else: self.ANIMMODE = False self.animListCard.hide() - - logger.info(f"Enable anim state to {self.ANIMMODE}") + logger.info(f"Enable anim state to {self.ANIMMODE}") def pixMainProcess(self, args): """Main process to run Convertex PIX exe file (Main)""" - + # check if scs path is not empty if self.SCS_FILES == "": logger.error("SCS file path is not set and operation canceled") @@ -289,7 +298,7 @@ def pixMainProcess(self, args): def handleMainOutput(self): """Handle output of ConverterPIX (Main)""" - + # get output data from process and decode it data = self.mainProcess.readAllStandardOutput() decoded = bytes(data).decode("utf-8") @@ -298,7 +307,7 @@ def handleMainOutput(self): # handle listdir mode if self.PIX_MODE == "listdir": - + if output[-1] == "-- done --": # add root item to navigation bar if len(self.SCS_FILES) < 2: @@ -322,7 +331,9 @@ def handleMainOutput(self): # create file list elif line.startswith("[F] "): # cheack suffix and only include specified in list26 - if line.endswith(self.SUFFIX_END) and not line.endswith((".pmg", ".pmc", ".pma")): + if line.endswith(self.SUFFIX_END) and not line.endswith( + (".pmg", ".pmc", ".pma") + ): file.append(os.path.relpath(line[4:], self.SUB_PATH)) # set list count to badges @@ -331,28 +342,42 @@ def handleMainOutput(self): # add items in list to list view for dir in folder: - self.folderList.addItem(QListWidgetItem(FluentIconBase.qicon(ScsHubIcon.FOLDER), dir)) - + self.folderList.addItem( + QListWidgetItem(FluentIconBase.qicon(ScsHubIcon.FOLDER), dir) + ) + # add items in list to list view and ignore it if in (-extract_d) mode if self.MODE != str(Argument.EXT_DIR): for file in file: if file.endswith(".pmd"): - self.fileList.addItem(QListWidgetItem(FluentIconBase.qicon(ScsHubIcon.MODEL), file)) + self.fileList.addItem( + QListWidgetItem(FluentIconBase.qicon(ScsHubIcon.MODEL), file) + ) elif file.endswith(".ppd"): - self.fileList.addItem(QListWidgetItem(FluentIconBase.qicon(ScsHubIcon.PREFAB), file)) + self.fileList.addItem( + QListWidgetItem(FluentIconBase.qicon(ScsHubIcon.PREFAB), file) + ) elif file.endswith(".tobj"): - self.fileList.addItem(QListWidgetItem(FluentIconBase.qicon(ScsHubIcon.TOBJ), file)) + self.fileList.addItem( + QListWidgetItem(FluentIconBase.qicon(ScsHubIcon.TOBJ), file) + ) elif file.endswith((".dds", ".png", ".jpg", ".mask")): - self.fileList.addItem(QListWidgetItem(FluentIconBase.qicon(ScsHubIcon.TEXTURE), file)) + self.fileList.addItem( + QListWidgetItem(FluentIconBase.qicon(ScsHubIcon.TEXTURE), file) + ) elif file.endswith((".sii", ".sui", ".txt", ".cfg", ".dat", ".soundref")): - self.fileList.addItem(QListWidgetItem(FluentIconBase.qicon(ScsHubIcon.TEXT), file)) + self.fileList.addItem( + QListWidgetItem(FluentIconBase.qicon(ScsHubIcon.TEXT), file) + ) else: - self.fileList.addItem(QListWidgetItem(FluentIconBase.qicon(ScsHubIcon.FILE), file)) + self.fileList.addItem( + QListWidgetItem(FluentIconBase.qicon(ScsHubIcon.FILE), file) + ) # handle custom mode elif self.PIX_MODE == "custom": @@ -361,22 +386,32 @@ def handleMainOutput(self): f = open("pix.log", "a") f.write("===== Start =====") for line in output[5:]: - f.write(f"{line}\n") + f.write(f"{line}\n") f.write("===== End =====\n\n") f.close() # handle warning and error and show infobar - if "" in decoded: self.infoBar("warning") - elif "" in decoded: self.infoBar("error") - elif "No" in decoded: self.infoBar("error") - elif "Unable" in decoded: self.infoBar("error") - elif "Cannot" in decoded: self.infoBar("error") - elif "Failed" in decoded: self.infoBar("error") - elif "readDir" in decoded: self.infoBar("error") - elif "Invalid parameters" in decoded: self.infoBar("error") - elif "Unknown filesystem type" in decoded: self.infoBar("error") - else: self.infoBar("success") - + if "" in decoded: + self.infoBar("warning") + elif "" in decoded: + self.infoBar("error") + elif "No" in decoded: + self.infoBar("error") + elif "Unable" in decoded: + self.infoBar("error") + elif "Cannot" in decoded: + self.infoBar("error") + elif "Failed" in decoded: + self.infoBar("error") + elif "readDir" in decoded: + self.infoBar("error") + elif "Invalid parameters" in decoded: + self.infoBar("error") + elif "Unknown filesystem type" in decoded: + self.infoBar("error") + else: + self.infoBar("success") + logger.info("Pix process ended and output saved in pix.log file") def handleMainState(self, state): @@ -395,8 +430,16 @@ def handleMainState(self, state): # running state if state_name == "Running": # show working infobar - self.workingInfobar = InfoBar.new(InfoBarIcon.INFORMATION, "Working", "Executing task", Qt.Horizontal, - False, -1, InfoBarPosition.TOP, self) + self.workingInfobar = InfoBar.new( + InfoBarIcon.INFORMATION, + "Working", + "Executing task", + Qt.Horizontal, + False, + -1, + InfoBarPosition.TOP, + self, + ) workingWgt = IndeterminateProgressRing(self) workingWgt.setFixedSize(22, 22) workingWgt.setStrokeWidth(4) @@ -408,12 +451,12 @@ def handleMainState(self, state): self.folderList.setDisabled(True) logger.info("Pix Process Running") - + # finish state elif state_name == "NotRunning": # close working infobar self.workingInfobar.close() - + # enable back, buttons and lists again self.runButton.setEnabled(True) self.fileList.setEnabled(True) @@ -425,10 +468,9 @@ def handleMainFinish(self): self.mainProcess = None - def pixAnimProcess(self, args): """Main process to run Convertex PIX exe file (Anim)""" - + # check if scs path is not empty if self.SCS_FILES == "": logger.error("SCS file path is not set and operation canceled") @@ -457,7 +499,7 @@ def handleAnimOutput(self): self.animList.clearSelection() self.animList.clear() self.SELECTED_ANIM_FILES = [] - + self.animList.addItem(QListWidgetItem(FluentIconBase.qicon(FluentIcon.UP), "..")) folder = [] @@ -481,7 +523,7 @@ def handleAnimOutput(self): # add items in list to list view for dir in folder: self.animList.addItem(QListWidgetItem(FluentIconBase.qicon(ScsHubIcon.FOLDER), dir)) - + for anim in anim: self.animList.addItem(QListWidgetItem(FluentIconBase.qicon(ScsHubIcon.ANIM), anim)) @@ -489,13 +531,14 @@ def handleAnimFinish(self): self.animProcess = None - def removeSubPathEnd(self): """Check and remove last selected filename in subpath if it exist""" if self.SUB_PATH.endswith(self.LAST_SELECTED_FILE): self.SUB_PATH = self.SUB_PATH.replace(self.LAST_SELECTED_FILE, "") - logger.info(f"Check and remove last selected file:({self.LAST_SELECTED_FILE}) in subpath if it exist and refresh list") + logger.info( + f"Check and remove last selected file:({self.LAST_SELECTED_FILE}) in subpath if it exist and refresh list" + ) def resetSubPath(self): """Reset subpath to / and run again and go back to the root of scs file""" @@ -510,18 +553,17 @@ def resetSubPath(self): def refreshSubPath(self): """Refresh current subpath without going to root of scs file""" - + logger.info("Refresh curent subpath without reset and refresh list") # check and remove last selected filename in subpath if it exist self.removeSubPathEnd() - + self.fileList.clearSelection() self.animList.clearSelection() self.listdirMode() self.animMode() - def switchDir(self, selected): """Switch to chosen directory in navigation bar""" @@ -532,7 +574,9 @@ def switchDir(self, selected): # find selected item index in saved subpath string and # delete all after item name itself and update new subpath if self.SUB_PATH != "/": - newDir = self.SUB_PATH[0:self.SUB_PATH.rfind(selected[:-2])+len(selected[:-2])].replace("\\", "/") + newDir = self.SUB_PATH[ + 0 : self.SUB_PATH.rfind(selected[:-2]) + len(selected[:-2]) + ].replace("\\", "/") self.SUB_PATH = newDir self.listdirMode() @@ -546,7 +590,7 @@ def indexDir(self, selected): if selected != "": # check and remove last selected filename in subpath if it exist self.removeSubPathEnd() - + # update subpath and add selected folder name to end of it self.SUB_PATH = os.path.join(self.SUB_PATH, selected).replace("\\", "/") self.LAST_SELECTED_FOLDER = self.SUB_PATH @@ -561,7 +605,7 @@ def indexDir(self, selected): def indexFile(self, selected): """Add selected file and update subpath""" - + # check and remove last selected filename in subpath if it exist self.removeSubPathEnd() @@ -571,7 +615,9 @@ def indexFile(self, selected): self.LAST_SELECTED_FILE = selected - logger.info(f'Add ({self.LAST_SELECTED_FILE}) to subpath and new subpath is "{self.SUB_PATH}"') + logger.info( + f'Add ({self.LAST_SELECTED_FILE}) to subpath and new subpath is "{self.SUB_PATH}"' + ) def indexAnimFolder(self, selected): """Add selected anim folder and update anim path""" @@ -605,19 +651,20 @@ def indexAnimFile(self): self.SELECTED_ANIM_FILES = selectedList - logger.info(f'Current anim list is ({self.SELECTED_ANIM_FILES})') - + logger.info(f"Current anim list is ({self.SELECTED_ANIM_FILES})") def getScsFiles(self): """Get SCS file paths""" - filePaths = QFileDialog().getOpenFileNames(self, "Select SCS Archives", filter="SCS Archives (*.zip *.scs)") + filePaths = QFileDialog().getOpenFileNames( + self, "Select SCS Archives", filter="SCS Archives (*.zip *.scs)" + ) if filePaths[0]: self.SCS_FILES = filePaths[0] InfoBar.success("Success", "SCS Archives selected", duration=1500, parent=self) logger.info(f'Set scs file paths to "{str(filePaths[0])}"') self.resetSubPath() - + # enable function after file selected if not self.SCS_FILES == []: self.selectOutputButton.setEnabled(True) @@ -633,7 +680,7 @@ def getScsFiles(self): self.openScsSuccessBadge = InfoBadge.success("", self, self.selectScsButton) self.openScsSuccessBadge.setFixedSize(9, 9) self.openScsSuccessBadge.show() - + def getExportPath(self): """Get Export path""" @@ -642,14 +689,20 @@ def getExportPath(self): self.EXPORT_PATH = dirPath logger.info(f'Set export path to "{dirPath}"') - def infoBar(self, type): """Show info bar""" match type: case "success": - success = InfoBar.success("Success", "Finished succesfully", - Qt.Vertical, True, 3000, InfoBarPosition.TOP_RIGHT, self) + success = InfoBar.success( + "Success", + "Finished succesfully", + Qt.Vertical, + True, + 3000, + InfoBarPosition.TOP_RIGHT, + self, + ) logger.info("Operation completed successfully") # if windows, show open button if platform == "win32": @@ -658,22 +711,29 @@ def infoBar(self, type): # check export path and if it is empty, replace it with scs path with "_exp" suffix if self.EXPORT_PATH == "": - outPath = f'{self.SCS_FILES[0]}_exp' + outPath = f"{self.SCS_FILES[0]}_exp" rootPath = outPath.replace("/", "\\") else: outPath = self.EXPORT_PATH rootPath = outPath.replace("/", "\\") subPath = self.LAST_SELECTED_FOLDER - folderPath = f'{outPath}{subPath}'.replace("/", "\\") + folderPath = f"{outPath}{subPath}".replace("/", "\\") openFolder.clicked.connect(lambda: Popen(f"explorer.exe {folderPath}")) openRoot.clicked.connect(lambda: Popen(f"explorer.exe {rootPath}")) success.addWidget(openFolder) success.addWidget(openRoot) - case "error": - error = InfoBar.error("Failed", "There is error in process\nCheck log file.", - Qt.Vertical, True, 4000, InfoBarPosition.TOP_RIGHT, self) + case "error": + error = InfoBar.error( + "Failed", + "There is error in process\nCheck log file.", + Qt.Vertical, + True, + 4000, + InfoBarPosition.TOP_RIGHT, + self, + ) logger.error("Operation completed with error, check pix.log file") if platform == "win32": openLog = PushButton("Open Log") @@ -681,15 +741,21 @@ def infoBar(self, type): error.addWidget(openLog) case "warning": - warning = InfoBar.warning("Failed", "There is warning in process\nCheck log file.", - Qt.Vertical, True, 4000, InfoBarPosition.TOP_RIGHT, self) + warning = InfoBar.warning( + "Failed", + "There is warning in process\nCheck log file.", + Qt.Vertical, + True, + 4000, + InfoBarPosition.TOP_RIGHT, + self, + ) logger.error("Operation completed with error, check pix.log file") if platform == "win32": openLog = PushButton("Open Log") openLog.clicked.connect(lambda: Popen("notepad.exe pix.log")) warning.addWidget(openLog) - def donwloadPix(self): downloader.start() @@ -702,13 +768,21 @@ def downloadPixStart(self): self.downloadPixButton.setDisabled(True) - self.downloadInfobar = InfoBar.new(InfoBarIcon.INFORMATION, "Working", "Downloading ConverterPIX", Qt.Horizontal, - False, -1, InfoBarPosition.TOP, self) + self.downloadInfobar = InfoBar.new( + InfoBarIcon.INFORMATION, + "Working", + "Downloading ConverterPIX", + Qt.Horizontal, + False, + -1, + InfoBarPosition.TOP, + self, + ) downloadWgt = IndeterminateProgressRing(self) downloadWgt.setFixedSize(22, 22) downloadWgt.setStrokeWidth(4) self.downloadInfobar.addWidget(downloadWgt) - + def downloadPixFinish(self, result): self.downloadInfobar.close() @@ -722,11 +796,13 @@ def downloadPixFinish(self, result): self.downloadPixButton.hide() self.downloadPixButton.setDisabled(True) self.selectScsButton.setEnabled(True) - + InfoBar.success("Success", "ConverterPIX downloaded", duration=2000, parent=self) logger.info("ConverterPix updated!") case 1: self.downloadPixButton.setEnabled(True) - InfoBar.error("Failed", "Error during downloading\nCheck internet", duration=2000, parent=self) \ No newline at end of file + InfoBar.error( + "Failed", "Error during downloading\nCheck internet", duration=2000, parent=self + ) diff --git a/scshub/view/interface/scs_interface.py b/scshub/view/interface/scs_interface.py index de860bc..c2ce51b 100644 --- a/scshub/view/interface/scs_interface.py +++ b/scshub/view/interface/scs_interface.py @@ -5,8 +5,17 @@ from PyQt5.QtCore import Qt, QProcess from PyQt5.QtWidgets import QWidget, QFileDialog, QVBoxLayout -from qfluentwidgets import (InfoBar, InfoBarPosition, InfoBarIcon, IndeterminateProgressRing, - PushButton, ToolTipFilter, FluentIcon, FluentIconBase, InfoBadge) +from qfluentwidgets import ( + InfoBar, + InfoBarPosition, + InfoBarIcon, + IndeterminateProgressRing, + PushButton, + ToolTipFilter, + FluentIcon, + FluentIconBase, + InfoBadge, +) from ..ui.scs_ui import Ui_SCS from ...common.tool import ScsHubIcon, downloader, SCS_URL, SCS_PATH @@ -21,7 +30,7 @@ class ScsInterface(QWidget, Ui_SCS): def __init__(self): super().__init__() - #check for log file and delete it + # check for log file and delete it if os.path.isfile("scs.log"): os.remove("scs.log") logger.info("Delete last scs.log file") @@ -61,7 +70,7 @@ def initUi(self): self.selectScsErrorBadge.setFixedSize(9, 9) self.selectScsErrorBadge.show() - self.selectScsSuccessBadge = InfoBadge() + self.selectScsSuccessBadge = InfoBadge() self.selectScsButton.clicked.connect(lambda: self.getScsFile()) @@ -70,7 +79,6 @@ def initUi(self): self.runButton.clicked.connect(lambda: self.runScsExtractor()) - def runScsExtractor(self): """Run SCSExtractor""" @@ -89,10 +97,9 @@ def runScsExtractor(self): self.mainProcess.start(command) self.mainProcess.waitForReadyRead(1) - def handleOutput(self): """Handle output of SCSExtractor""" - + # get output data from process and decode it data = self.mainProcess.readAllStandardOutput() decoded = bytes(data).decode("utf-8") @@ -103,14 +110,16 @@ def handleOutput(self): f = open("scs.log", "a") f.write("===== Start =====\n") for line in output: - f.write(f"{line}\n") + f.write(f"{line}\n") f.write("===== End =====\n\n") f.close() # handle error and show infobar - if "*** ERROR ***" in decoded: self.infoBar("error") - else: self.infoBar("success") - + if "*** ERROR ***" in decoded: + self.infoBar("error") + else: + self.infoBar("success") + logger.info("SCSExtractor ended and output saved in scs.log file") def handleState(self, state): @@ -127,8 +136,16 @@ def handleState(self, state): # running state if state_name == "Running": # show working infobar - self.workingInfobar = InfoBar.new(InfoBarIcon.INFORMATION, "Working", "Exracting files", Qt.Horizontal, - False, -1, InfoBarPosition.TOP, self) + self.workingInfobar = InfoBar.new( + InfoBarIcon.INFORMATION, + "Working", + "Exracting files", + Qt.Horizontal, + False, + -1, + InfoBarPosition.TOP, + self, + ) workingWgt = IndeterminateProgressRing(self) workingWgt.setFixedSize(22, 22) workingWgt.setStrokeWidth(4) @@ -138,12 +155,12 @@ def handleState(self, state): self.runButton.setDisabled(True) logger.info("SCSExtractor Running") - + # finish state elif state_name == "NotRunning": # close working infobar self.workingInfobar.close() - + # enable back, buttons and lists again self.runButton.setEnabled(True) @@ -153,12 +170,13 @@ def handleFinish(self): self.mainProcess = None - def getScsFile(self): """Get SCS file path""" if platform == "win32": - filePath = QFileDialog().getOpenFileName(self, "Select SCS Archive", filter="SCS Archive (*.zip *.scs)") + filePath = QFileDialog().getOpenFileName( + self, "Select SCS Archive", filter="SCS Archive (*.zip *.scs)" + ) if filePath[0]: self.SCS_FILE = filePath[0] @@ -173,7 +191,7 @@ def getScsFile(self): InfoBar.success("Success", "SCS Archives selected", duration=1500, parent=self) logger.info(f'Set scs file paths to "{str(filePath[0])}"') - + # enable function after file selected if not self.SCS_FILE == []: self.selectOutputButton.setEnabled(True) @@ -185,7 +203,7 @@ def getScsFile(self): self.selectScsSuccessBadge.show() else: self.infoBar("platform") - + def getExportPath(self): """Get Export path""" @@ -194,17 +212,23 @@ def getExportPath(self): self.EXPORT_PATH = dirPath self.outputPathLabel.setText(self.EXPORT_PATH) - - logger.info(f'Set export path to "{dirPath}"') + logger.info(f'Set export path to "{dirPath}"') def infoBar(self, type): """Show info bar""" match type: case "success": - success = InfoBar.success("Success", "Finished succesfully", - Qt.Vertical, True, 3000, InfoBarPosition.TOP_RIGHT, self) + success = InfoBar.success( + "Success", + "Finished succesfully", + Qt.Vertical, + True, + 3000, + InfoBarPosition.TOP_RIGHT, + self, + ) logger.info("Operation completed successfully") # if windows, show open button if platform == "win32": @@ -213,9 +237,16 @@ def infoBar(self, type): openFolder.clicked.connect(lambda: Popen(f"explorer.exe {folderPath}")) success.addWidget(openFolder) - case "error": - error = InfoBar.error("Failed", "There is error in process\nCheck log file.", - Qt.Vertical, True, 4000, InfoBarPosition.TOP_RIGHT, self) + case "error": + error = InfoBar.error( + "Failed", + "There is error in process\nCheck log file.", + Qt.Vertical, + True, + 4000, + InfoBarPosition.TOP_RIGHT, + self, + ) logger.error("Operation completed with error, check scs.log file") if platform == "win32": openLog = PushButton("Open Log") @@ -223,16 +254,27 @@ def infoBar(self, type): error.addWidget(openLog) case "warning": - warning = InfoBar.warning("Failed", "There is warning in process\nCheck log file.", - Qt.Vertical, True, 4000, InfoBarPosition.TOP_RIGHT, self) + warning = InfoBar.warning( + "Failed", + "There is warning in process\nCheck log file.", + Qt.Vertical, + True, + 4000, + InfoBarPosition.TOP_RIGHT, + self, + ) logger.error("Operation completed with error, check scs.log file") if platform == "win32": openLog = PushButton("Open Log") openLog.clicked.connect(lambda: Popen("notepad.exe pix.log")) warning.addWidget(openLog) case "platform": - InfoBar.error("Error", f"Not work in {platform}\nSCS extractor only work in windows", duration=4000, parent=self) - + InfoBar.error( + "Error", + f"Not work in {platform}\nSCS extractor only work in windows", + duration=4000, + parent=self, + ) def donwloadScs(self): if platform == "win32": @@ -248,13 +290,21 @@ def donwloadScsStart(self): self.downloadScsButton.setDisabled(True) - self.downloadInfobar = InfoBar.new(InfoBarIcon.INFORMATION, "Working", "Downloading SCSExtractor", Qt.Horizontal, - False, -1, InfoBarPosition.TOP, self) + self.downloadInfobar = InfoBar.new( + InfoBarIcon.INFORMATION, + "Working", + "Downloading SCSExtractor", + Qt.Horizontal, + False, + -1, + InfoBarPosition.TOP, + self, + ) downloadWgt = IndeterminateProgressRing(self) downloadWgt.setFixedSize(22, 22) downloadWgt.setStrokeWidth(4) self.downloadInfobar.addWidget(downloadWgt) - + def donwloadScsFinish(self, result): self.downloadInfobar.close() @@ -268,11 +318,13 @@ def donwloadScsFinish(self, result): self.downloadScsButton.hide() self.downloadScsButton.setDisabled(True) self.selectScsButton.setEnabled(True) - + InfoBar.success("Success", "SCSExtractor downloaded", duration=2000, parent=self) logger.info("SCSExtractor updated!") case 1: self.downloadScsButton.setEnabled(True) - InfoBar.error("Failed", "Error during downloading\nCheck internet", duration=2000, parent=self) \ No newline at end of file + InfoBar.error( + "Failed", "Error during downloading\nCheck internet", duration=2000, parent=self + ) diff --git a/scshub/view/interface/setting_interface.py b/scshub/view/interface/setting_interface.py index 1d08a1d..7c31859 100644 --- a/scshub/view/interface/setting_interface.py +++ b/scshub/view/interface/setting_interface.py @@ -1,22 +1,46 @@ -from PyQt5.QtCore import Qt, QUrl, QSize, QRect +from PyQt5.QtCore import Qt, QUrl, QSize from PyQt5.QtGui import QDesktopServices from PyQt5.QtWidgets import QWidget, QLabel, QSizePolicy, QSpacerItem, QHBoxLayout, QVBoxLayout -from qfluentwidgets import (SettingCardGroup, SettingCard, CustomColorSettingCard, - PrimaryPushSettingCard, OptionsSettingCard, HyperlinkLabel, - ComboBoxSettingCard, SwitchSettingCard, SimpleCardWidget, - ExpandLayout, ScrollArea, InfoBar, FluentIcon, AvatarWidget, - BodyLabel, StrongBodyLabel, setTheme, setThemeColor) +from qfluentwidgets import ( + SettingCardGroup, + SettingCard, + CustomColorSettingCard, + PrimaryPushSettingCard, + OptionsSettingCard, + HyperlinkLabel, + ComboBoxSettingCard, + SwitchSettingCard, + SimpleCardWidget, + ExpandLayout, + ScrollArea, + InfoBar, + FluentIcon, + AvatarWidget, + BodyLabel, + StrongBodyLabel, + setTheme, + setThemeColor, +) from ...common.config import cfg, isWin11 -from ...common.tool import StyleSheet, signalBus, VERSION, YEAR, SCSHUB_FEEDBACK_URL, GITHUB, TELEGRAM, INSTAGRAM +from ...common.tool import ( + StyleSheet, + signalBus, + VERSION, + YEAR, + SCSHUB_FEEDBACK_URL, + GITHUB, + TELEGRAM, + INSTAGRAM, +) class SettingInterface(ScrollArea): def __init__(self, parent=None): super().__init__(parent=parent) - + self.scrollWidget = QWidget() self.expandLayout = ExpandLayout(self.scrollWidget) @@ -31,65 +55,65 @@ def __init__(self, parent=None): def cards(self): self.personalGroup = SettingCardGroup(self.tr("Personalization"), self.scrollWidget) - + self.micaCard = SwitchSettingCard( FluentIcon.TRANSPARENT, self.tr("Mica effect"), self.tr("Apply semi transparent to windows and surfaces"), cfg.micaEnabled, - self.personalGroup + self.personalGroup, ) self.themeCard = OptionsSettingCard( cfg.themeMode, FluentIcon.BRUSH, self.tr("Application theme"), self.tr("Change the appearance of your application"), - texts=[ - self.tr("Light"), self.tr("Dark"), - self.tr("Use system setting") - ], - parent=self.personalGroup + texts=[self.tr("Light"), self.tr("Dark"), self.tr("Use system setting")], + parent=self.personalGroup, ) self.themeColorCard = CustomColorSettingCard( cfg.themeColor, FluentIcon.PALETTE, self.tr("Theme color"), self.tr("Change the theme color of you application"), - self.personalGroup + self.personalGroup, ) self.zoomCard = ComboBoxSettingCard( cfg.dpiScale, FluentIcon.ZOOM, self.tr("Interface zoom"), self.tr("Change the size of widgets and fonts"), - texts=[ - "100%", "125%", "150%", "175%", "200%", - self.tr("Use system setting")], - parent=self.personalGroup + texts=["100%", "125%", "150%", "175%", "200%", self.tr("Use system setting")], + parent=self.personalGroup, ) self.languageCard = ComboBoxSettingCard( cfg.language, FluentIcon.LANGUAGE, self.tr("Language"), self.tr("Set your preferred language for UI"), - texts=['English', self.tr('Use system setting')], - parent=self.personalGroup + texts=["English", self.tr("Use system setting")], + parent=self.personalGroup, ) self.aboutGroup = SettingCardGroup(self.tr("About"), self.scrollWidget) - + self.feedbackCard = PrimaryPushSettingCard( self.tr("Provide feedback"), FluentIcon.FEEDBACK, self.tr("Provide feedback"), self.tr("Help us improve SCS Hub by providing feedback"), - self.aboutGroup + self.aboutGroup, ) self.aboutCard = SettingCard( FluentIcon.INFO, self.tr("About"), - "© " + self.tr("Copyright") + f" {YEAR}, AmirMahdavi. " + self.tr("Version") + " " + VERSION, - self.aboutGroup + "© " + + self.tr("Copyright") + + f" {YEAR}, AmirMahdavi. " + + self.tr("Version") + + " " + + VERSION, + self.aboutGroup, ) def profile(self): @@ -114,7 +138,7 @@ def profile(self): self.textLayout.setObjectName("textLayout") spacerItem1 = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Minimum) - + self.nameLabel = StrongBodyLabel(self.profileCard) self.nameLabel.setObjectName("nameLabel") self.nameLabel.setText("Amir Mahdavi") @@ -126,7 +150,7 @@ def profile(self): self.linkLayout = QHBoxLayout() self.linkLayout.setObjectName("linkLayout") self.linkLayout.setSpacing(16) - + self.githubLink = HyperlinkLabel("GitHub", self.profileCard) self.githubLink.setObjectName("githubLink") self.githubLink.setUrl(GITHUB) @@ -135,7 +159,7 @@ def profile(self): self.telegramLink.setObjectName("telegramLink") self.telegramLink.setUrl(TELEGRAM) - self.instagramLink = HyperlinkLabel("Instagram",self.profileCard) + self.instagramLink = HyperlinkLabel("Instagram", self.profileCard) self.instagramLink.setObjectName("instagramLink") self.instagramLink.setUrl(INSTAGRAM) @@ -197,7 +221,7 @@ def restartInfoBar(self): self.tr("Success"), self.tr("Configuration takes effect after restart"), duration=1500, - parent=self + parent=self, ) def connectSignalToSlot(self): @@ -212,4 +236,5 @@ def connectSignalToSlot(self): # about self.feedbackCard.clicked.connect( - lambda: QDesktopServices.openUrl(QUrl(SCSHUB_FEEDBACK_URL))) + lambda: QDesktopServices.openUrl(QUrl(SCSHUB_FEEDBACK_URL)) + ) diff --git a/scshub/view/ui/pix_ui.py b/scshub/view/ui/pix_ui.py index 33a3fbb..25107f1 100644 --- a/scshub/view/ui/pix_ui.py +++ b/scshub/view/ui/pix_ui.py @@ -1,14 +1,19 @@ -# -*- coding: utf-8 -*- +from PyQt5 import QtCore, QtWidgets -# Form implementation generated from reading ui file 'c:\Users\Amir\Desktop\SCSHub\scshub\view\ui\pix_ui.ui' -# -# Created by: PyQt5 UI code generator 5.15.9 -# -# WARNING: Any manual changes made to this file will be lost when pyuic5 is -# run again. Do not edit this file unless you know what you are doing. - - -from PyQt5 import QtCore, QtGui, QtWidgets +from qfluentwidgets import ( + BreadcrumbBar, + CheckBox, + ElevatedCardWidget, + InfoBadge, + ListWidget, + PrimaryPushButton, + PushButton, + RadioButton, + SimpleCardWidget, + StrongBodyLabel, + ToolButton, + VerticalSeparator, +) class Ui_PIX(object): @@ -50,12 +55,16 @@ def setupUi(self, PIX): self.bottomButtonLayout.addWidget(self.resetButton) self.buttonLayout.addLayout(self.bottomButtonLayout) self.horizontalLayout.addLayout(self.buttonLayout) - spacerItem = QtWidgets.QSpacerItem(13, 20, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Minimum) + spacerItem = QtWidgets.QSpacerItem( + 13, 20, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Minimum + ) self.horizontalLayout.addItem(spacerItem) self.VerticalSeparator = VerticalSeparator(PIX) self.VerticalSeparator.setObjectName("VerticalSeparator") self.horizontalLayout.addWidget(self.VerticalSeparator) - spacerItem1 = QtWidgets.QSpacerItem(13, 20, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Minimum) + spacerItem1 = QtWidgets.QSpacerItem( + 13, 20, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Minimum + ) self.horizontalLayout.addItem(spacerItem1) self.radioButtonLayout = QtWidgets.QGridLayout() self.radioButtonLayout.setObjectName("radioButtonLayout") @@ -117,7 +126,9 @@ def setupUi(self, PIX): self.folderLabel = StrongBodyLabel(self.folderHeaderCard) self.folderLabel.setObjectName("folderLabel") self.folderListLayout.addWidget(self.folderLabel) - spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + spacerItem2 = QtWidgets.QSpacerItem( + 40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum + ) self.folderListLayout.addItem(spacerItem2) self.folderCountBadge = InfoBadge(self.folderHeaderCard) self.folderCountBadge.setObjectName("folderCountBadge") @@ -141,7 +152,9 @@ def setupUi(self, PIX): self.fileLabel = StrongBodyLabel(self.fileHeaderCard) self.fileLabel.setObjectName("fileLabel") self.fileListLayout.addWidget(self.fileLabel) - spacerItem3 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + spacerItem3 = QtWidgets.QSpacerItem( + 40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum + ) self.fileListLayout.addItem(spacerItem3) self.fileCountBadge = InfoBadge(self.fileHeaderCard) self.fileCountBadge.setObjectName("fileCountBadge") @@ -165,7 +178,9 @@ def setupUi(self, PIX): self.animLabel = StrongBodyLabel(self.animHeaderCard) self.animLabel.setObjectName("animLabel") self.animListLayout.addWidget(self.animLabel) - spacerItem4 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + spacerItem4 = QtWidgets.QSpacerItem( + 40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum + ) self.animListLayout.addItem(spacerItem4) self.animCountBadge = InfoBadge(self.animHeaderCard) self.animCountBadge.setObjectName("animCountBadge") @@ -209,4 +224,3 @@ def retranslateUi(self, PIX): self.fileCountBadge.setText("0") self.animLabel.setText(self.tr("Anim")) self.animCountBadge.setText("0") -from qfluentwidgets import BreadcrumbBar, CheckBox, ElevatedCardWidget, InfoBadge, ListWidget, PrimaryPushButton, PushButton, RadioButton, SimpleCardWidget, StrongBodyLabel, ToolButton, VerticalSeparator diff --git a/scshub/view/ui/scs_ui.py b/scshub/view/ui/scs_ui.py index c80390d..46a5b76 100644 --- a/scshub/view/ui/scs_ui.py +++ b/scshub/view/ui/scs_ui.py @@ -1,15 +1,7 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file 'c:\Users\Amir\Desktop\SCSHub\scshub\view\ui\scs_ui.ui' -# -# Created by: PyQt5 UI code generator 5.15.9 -# -# WARNING: Any manual changes made to this file will be lost when pyuic5 is -# run again. Do not edit this file unless you know what you are doing. - - from PyQt5 import QtCore, QtGui, QtWidgets +from qfluentwidgets import BodyLabel, PrimaryPushButton, PushButton, StrongBodyLabel + class Ui_SCS(object): def setupUi(self, SCS): @@ -43,7 +35,9 @@ def setupUi(self, SCS): self.scsFileLayout = QtWidgets.QHBoxLayout() self.scsFileLayout.setObjectName("scsFileLayout") self.scsFileLabel = BodyLabel(SCS) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) + sizePolicy = QtWidgets.QSizePolicy( + QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred + ) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.scsFileLabel.sizePolicy().hasHeightForWidth()) @@ -57,7 +51,9 @@ def setupUi(self, SCS): self.outputLayout = QtWidgets.QHBoxLayout() self.outputLayout.setObjectName("outputLayout") self.outputLabel = BodyLabel(SCS) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) + sizePolicy = QtWidgets.QSizePolicy( + QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred + ) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.outputLabel.sizePolicy().hasHeightForWidth()) @@ -68,7 +64,9 @@ def setupUi(self, SCS): self.outputPathLabel.setObjectName("outputPathLabel") self.outputLayout.addWidget(self.outputPathLabel) self.verticalLayout.addLayout(self.outputLayout) - spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + spacerItem = QtWidgets.QSpacerItem( + 20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding + ) self.verticalLayout.addItem(spacerItem) self.retranslateUi(SCS) @@ -86,4 +84,3 @@ def retranslateUi(self, SCS): self.scsFilePathLabel.setText(_translate("SCS", "-")) self.outputLabel.setText(_translate("SCS", "Output Folder:")) self.outputPathLabel.setText(_translate("SCS", "-")) -from qfluentwidgets import BodyLabel, PrimaryPushButton, PushButton, StrongBodyLabel