diff --git a/.gitignore b/.gitignore index ceeed0a..cad1753 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ output/ dist/ dist.zip language.txt -theme.txt \ No newline at end of file +theme.txt +presets.json \ No newline at end of file diff --git a/src/darkmode.css b/src/darkmode.css index 1d5f7d4..1d8e5b9 100644 --- a/src/darkmode.css +++ b/src/darkmode.css @@ -69,4 +69,40 @@ QComboBox { border: 1px solid #555555; padding: 5px; color: #ffffff; -} \ No newline at end of file +} + +/* Presets Section */ +#presetsGroupBox { + background-color: #2b2b2b; + border: 1px solid #555555; + border-radius: 5px; + padding: 10px; + color: #ffffff; +} + +QListWidget#presetsList { + background-color: #1e1e1e; + border: 1px solid #555555; + color: #ffffff; + padding: 5px; + selection-background-color: #555555; +} + +QPushButton#savePresetButton, QPushButton#renamePresetButton, QPushButton#deletePresetButton { + font-size: 9pt; + padding: 8px; + background-color: #666666; + color: #ffffff; + border: none; + border-radius: 5px; + font: bold; + margin-top: 5px; +} + +QPushButton#savePresetButton:hover, QPushButton#renamePresetButton:hover, QPushButton#deletePresetButton:hover { + background-color: #444444; +} + +QPushButton#savePresetButton:disabled, QPushButton#renamePresetButton:disabled, QPushButton#deletePresetButton:disabled { + background-color: #555555; +} diff --git a/src/main.py b/src/main.py index 0e1512f..2f076aa 100644 --- a/src/main.py +++ b/src/main.py @@ -2,8 +2,11 @@ import time import random import string -from PyQt5.QtWidgets import QApplication, QMainWindow, QMessageBox +import json +import os +from PyQt5.QtWidgets import QApplication, QMainWindow, QMessageBox, QListWidgetItem from PyQt5.QtCore import QTimer, Qt, QThread, pyqtSignal +from PyQt5.QtWidgets import QInputDialog from ui import Ui_MainWindow from pynput.keyboard import Controller, Key @@ -85,7 +88,17 @@ def __init__(self): self.stopButton.clicked.connect(self.stop_typing) self.languageComboBox.currentTextChanged.connect(self.change_language) self.lightModeCheckBox.toggled.connect(self.toggleTheme) + + # Presets Buttons + self.savePresetButton.clicked.connect(self.save_preset) + self.renamePresetButton.clicked.connect(self.rename_preset) + self.deletePresetButton.clicked.connect(self.delete_preset) + self.presetsList.itemDoubleClicked.connect(self.load_preset) + self.thread = None + self.presets = {} + self.presets_file = "presets.json" + self.load_presets() def start_typing(self): text = self.textEdit.toPlainText() @@ -129,7 +142,7 @@ def stop_typing(self): self.thread.wait() self.thread = None self.typing_finished() - + def toggleTheme(self): if self.lightModeCheckBox.isChecked(): self.setStyleSheet(open("style.css").read()) @@ -140,10 +153,117 @@ def toggleTheme(self): def save_theme_based_on_last_choice(self): with open("theme.txt", "w", encoding="utf-8") as f: f.write("light" if self.lightModeCheckBox.isChecked() else "dark") - + + # Presets Methods + def load_presets(self): + if os.path.exists(self.presets_file): + try: + with open(self.presets_file, "r", encoding="utf-8") as f: + self.presets = json.load(f) + for preset_name in self.presets: + self.presetsList.addItem(preset_name) + except Exception as e: + QMessageBox.critical(self, "Error", f"Failed to load presets: {e}") + else: + self.presets = {} + + def save_presets_to_file(self): + try: + with open(self.presets_file, "w", encoding="utf-8") as f: + json.dump(self.presets, f, indent=4) + except Exception as e: + QMessageBox.critical(self, "Error", f"Failed to save presets: {e}") + + def save_preset(self): + preset_name, ok = self.get_text_input("Save Preset", "Enter a name for the preset:") + if ok and preset_name: + if preset_name in self.presets: + QMessageBox.warning(self, "Warning", "A preset with this name already exists.") + return + self.presets[preset_name] = self.current_settings() + self.presetsList.addItem(preset_name) + self.save_presets_to_file() + QMessageBox.information(self, "Success", f"Preset '{preset_name}' saved successfully.") + + def rename_preset(self): + selected_item = self.presetsList.currentItem() + if not selected_item: + QMessageBox.warning(self, "Warning", "Please select a preset to rename.") + return + old_name = selected_item.text() + new_name, ok = self.get_text_input("Rename Preset", "Enter a new name for the preset:", old_name) + if ok and new_name: + if new_name in self.presets: + QMessageBox.warning(self, "Warning", "A preset with this name already exists.") + return + self.presets[new_name] = self.presets.pop(old_name) + selected_item.setText(new_name) + self.save_presets_to_file() + QMessageBox.information(self, "Success", f"Preset renamed to '{new_name}' successfully.") + + def delete_preset(self): + selected_item = self.presetsList.currentItem() + if not selected_item: + QMessageBox.warning(self, "Warning", "Please select a preset to delete.") + return + preset_name = selected_item.text() + confirm = QMessageBox.question(self, "Confirm Delete", f"Are you sure you want to delete preset '{preset_name}'?", QMessageBox.Yes | QMessageBox.No) + if confirm == QMessageBox.Yes: + self.presets.pop(preset_name, None) + self.presetsList.takeItem(self.presetsList.row(selected_item)) + self.save_presets_to_file() + QMessageBox.information(self, "Success", f"Preset '{preset_name}' deleted successfully.") + + def load_preset(self, item): + preset_name = item.text() + settings = self.presets.get(preset_name) + if settings: + self.apply_settings(settings) + QMessageBox.information(self, "Preset Loaded", f"Preset '{preset_name}' has been loaded.") + else: + QMessageBox.warning(self, "Warning", f"Preset '{preset_name}' not found.") + + def current_settings(self): + return { + "delay": self.delaySpinBox.value(), + "interval": self.intervalSpinBox.value(), + "type_enter": self.enterCheckBox.isChecked(), + "chars_per_stroke": self.charPerStrokeSpinBox.value(), + "randomize_interval": self.randomizeIntervalCheckBox.isChecked(), + "mistake_percentage": self.mistakePercentageSpinBox.value(), + "light_mode": self.lightModeCheckBox.isChecked(), + "language": self.current_language + } + + def apply_settings(self, settings): + self.delaySpinBox.setValue(settings.get("delay", 0)) + self.intervalSpinBox.setValue(settings.get("interval", 0.0)) + self.enterCheckBox.setChecked(settings.get("type_enter", False)) + self.charPerStrokeSpinBox.setValue(settings.get("chars_per_stroke", 1)) + self.randomizeIntervalCheckBox.setChecked(settings.get("randomize_interval", False)) + self.mistakePercentageSpinBox.setValue(settings.get("mistake_percentage", 0)) + self.lightModeCheckBox.setChecked(settings.get("light_mode", True)) + self.toggleTheme() + language = settings.get("language", "English") + index = self.languageComboBox.findText(language + " - " + self.translations.get(language, {}).get("original", "Unknown")) + if index != -1: + self.languageComboBox.setCurrentIndex(index) + + def get_text_input(self, title, label, default_text=""): + text, ok = QInputDialog.getText(self, title, label, text=default_text) + return text, ok + if __name__ == "__main__": app = QApplication(sys.argv) - app.setStyleSheet(open("style.css").read()) + if os.path.exists("theme.txt"): + with open("theme.txt", "r", encoding="utf-8") as f: + theme = f.read() + if theme == "light": + app.setStyleSheet(open("style.css").read()) + else: + app.setStyleSheet(open("darkmode.css").read()) + else: + app.setStyleSheet(open("style.css").read()) window = MainWindow() window.show() - sys.exit(app.exec_()) \ No newline at end of file + sys.exit(app.exec_()) diff --git a/src/style.css b/src/style.css index 7d579cc..b2bab08 100644 --- a/src/style.css +++ b/src/style.css @@ -71,3 +71,39 @@ QComboBox { padding: 5px; color: #000000; } + +/* Presets Section */ +#presetsGroupBox { + background-color: #e0e0e0; + border: 1px solid #cccccc; + border-radius: 5px; + padding: 10px; + color: #000000; +} + +QListWidget#presetsList { + background-color: #f9f9f9; + border: 1px solid #cccccc; + color: #000000; + padding: 5px; + selection-background-color: #d3d3d3; +} + +QPushButton#savePresetButton, QPushButton#renamePresetButton, QPushButton#deletePresetButton { + font-size: 9pt; + padding: 8px; + background-color: #f9d039; + color: #ffffff; + border: none; + border-radius: 5px; + font: bold; + margin-top: 5px; +} + +QPushButton#savePresetButton:hover, QPushButton#renamePresetButton:hover, QPushButton#deletePresetButton:hover { + background-color: #ffa500; +} + +QPushButton#savePresetButton:disabled, QPushButton#renamePresetButton:disabled, QPushButton#deletePresetButton:disabled { + background-color: #e0e0e0; +} diff --git a/src/translations/Chinese.json b/src/translations/Chinese.json index 72a7f30..af66d0b 100644 --- a/src/translations/Chinese.json +++ b/src/translations/Chinese.json @@ -26,7 +26,7 @@ "text": "停止打字" }, { - "component": "lightModeCheckbox", + "component": "lightModeCheckBox", "text": "轻模式" }, { @@ -36,6 +36,18 @@ { "component": "mistakePercentageLabel", "text": "错误百分比:" + }, + { + "component": "savePresetButton", + "text": "保存预设" + }, + { + "component": "renamePresetButton", + "text": "重命名预设" + }, + { + "component": "deletePresetButton", + "text": "删除预设" } ] } diff --git a/src/translations/English.json b/src/translations/English.json index f1e6f36..dbcd5f5 100644 --- a/src/translations/English.json +++ b/src/translations/English.json @@ -36,6 +36,18 @@ { "component": "mistakePercentageLabel", "text": "Mistake Percentage:" + }, + { + "component": "savePresetButton", + "text": "Save Preset" + }, + { + "component": "renamePresetButton", + "text": "Rename Preset" + }, + { + "component": "deletePresetButton", + "text": "Delete Preset" } ] -} \ No newline at end of file +} diff --git a/src/translations/French.json b/src/translations/French.json index 4866a55..287aa0a 100644 --- a/src/translations/French.json +++ b/src/translations/French.json @@ -36,6 +36,18 @@ { "component": "mistakePercentageLabel", "text": "Pourcentage d'erreur :" + }, + { + "component": "savePresetButton", + "text": "Enregistrer le préréglage" + }, + { + "component": "renamePresetButton", + "text": "Renommer le préréglage" + }, + { + "component": "deletePresetButton", + "text": "Supprimer le préréglage" } ] -} \ No newline at end of file +} diff --git a/src/translations/Norwegian.json b/src/translations/Norwegian.json index eaf885c..219cb92 100644 --- a/src/translations/Norwegian.json +++ b/src/translations/Norwegian.json @@ -19,7 +19,7 @@ }, { "component": "startButton", - "text": "Begynn Å Skrive " + "text": "Begynn Å Skrive" }, { "component": "stopButton", @@ -36,6 +36,18 @@ { "component": "mistakePercentageLabel", "text": "Feilprosent:" + }, + { + "component": "savePresetButton", + "text": "Lagre forhåndsinnstilling" + }, + { + "component": "renamePresetButton", + "text": "Gi nytt navn på forhåndsinnstilling" + }, + { + "component": "deletePresetButton", + "text": "Slett forhåndsinnstilling" } ] -} \ No newline at end of file +} diff --git a/src/ui.py b/src/ui.py index 595a468..f30b213 100644 --- a/src/ui.py +++ b/src/ui.py @@ -20,9 +20,9 @@ def __init__(self): def setupUi(self, MainWindow): MainWindow.setObjectName("MainWindow") - MainWindow.resize(600, 600) - MainWindow.setMinimumSize(QtCore.QSize(600, 600)) - MainWindow.setMaximumSize(QtCore.QSize(600, 600)) + MainWindow.resize(800, 600) # Increased width to accommodate presets + MainWindow.setMinimumSize(QtCore.QSize(800, 700)) + MainWindow.setMaximumSize(QtCore.QSize(800, 700)) MainWindow.setWindowIcon(QtGui.QIcon("logo.png")) if sys.platform == "win32" and hasattr(ctypes.windll, "shell32"): ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("Chainsaw Human Typing") @@ -30,17 +30,21 @@ def setupUi(self, MainWindow): self.centralwidget.setObjectName("centralwidget") self.horizontalLayoutWidget = QtWidgets.QWidget(self.centralwidget) - self.horizontalLayoutWidget.setGeometry(QtCore.QRect(20, 20, 560, 560)) + self.horizontalLayoutWidget.setGeometry(QtCore.QRect(10, 10, 781, 681)) self.horizontalLayoutWidget.setObjectName("horizontalLayoutWidget") - self.horizontalLayout = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget) - self.horizontalLayout.setContentsMargins(0, 0, 0, 0) - self.horizontalLayout.setObjectName("horizontalLayout") + self.mainLayout = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget) + self.mainLayout.setContentsMargins(0, 0, 0, 0) + self.mainLayout.setObjectName("mainLayout") + + # Left Side Layout (TextEdit and Settings) + self.leftLayout = QtWidgets.QVBoxLayout() + self.leftLayout.setObjectName("leftLayout") self.textEdit = PlainTextEdit(self.horizontalLayoutWidget) self.textEdit.setObjectName("textEdit") self.textEdit.setAcceptRichText(False) - self.horizontalLayout.addWidget(self.textEdit) + self.leftLayout.addWidget(self.textEdit) self.settingsLayout = QtWidgets.QVBoxLayout() self.settingsLayout.setObjectName("settingsLayout") @@ -115,7 +119,30 @@ def setupUi(self, MainWindow): self.lightModeCheckBox.setChecked(True) self.settingsLayout.addWidget(self.lightModeCheckBox) - self.horizontalLayout.addLayout(self.settingsLayout) + self.leftLayout.addLayout(self.settingsLayout) + self.mainLayout.addLayout(self.leftLayout, 3) + + # Right Side Layout (Presets) + self.presetsLayout = QtWidgets.QVBoxLayout() + self.presetsLayout.setObjectName("presetsLayout") + + self.savePresetButton = QtWidgets.QPushButton(self.horizontalLayoutWidget) + self.savePresetButton.setObjectName("savePresetButton") + self.presetsLayout.addWidget(self.savePresetButton) + + self.presetsList = QtWidgets.QListWidget(self.horizontalLayoutWidget) + self.presetsList.setObjectName("presetsList") + self.presetsLayout.addWidget(self.presetsList) + + self.renamePresetButton = QtWidgets.QPushButton(self.horizontalLayoutWidget) + self.renamePresetButton.setObjectName("renamePresetButton") + self.presetsLayout.addWidget(self.renamePresetButton) + + self.deletePresetButton = QtWidgets.QPushButton(self.horizontalLayoutWidget) + self.deletePresetButton.setObjectName("deletePresetButton") + self.presetsLayout.addWidget(self.deletePresetButton) + + self.mainLayout.addLayout(self.presetsLayout, 1) MainWindow.setCentralWidget(self.centralwidget) QtCore.QMetaObject.connectSlotsByName(MainWindow) @@ -129,6 +156,8 @@ def setupUi(self, MainWindow): def load_translations(self): self.translations = {} translations_dir = "translations" + if not os.path.exists(translations_dir): + os.makedirs(translations_dir) for filename in os.listdir(translations_dir): if filename.endswith(".json"): language_code = filename[:-5] # Remove .json extension @@ -160,8 +189,10 @@ def change_language(self): self.update_ui_text() def update_ui_text(self): - translation = self.translations.get(self.current_language, self.translations["English"]) - for item in translation["MainWindow"]: + translation = self.translations.get(self.current_language, self.translations.get("English", {})) + if not translation: + return + for item in translation.get("MainWindow", []): component = getattr(self, item["component"], None) if component: - component.setText(item["text"]) \ No newline at end of file + component.setText(item["text"])