diff --git a/portprotonqt/context_menu_manager.py b/portprotonqt/context_menu_manager.py
index 2c84b74..04c7f8f 100644
--- a/portprotonqt/context_menu_manager.py
+++ b/portprotonqt/context_menu_manager.py
@@ -8,7 +8,7 @@ import logging
import orjson
import psutil
import signal
-from PySide6.QtWidgets import QMessageBox, QDialog, QMenu
+from PySide6.QtWidgets import QMessageBox, QDialog, QMenu, QLineEdit, QApplication
from PySide6.QtCore import QUrl, QPoint, QObject, Signal, Qt
from PySide6.QtGui import QDesktopServices, QIcon
from portprotonqt.localization import _
@@ -1080,3 +1080,69 @@ Icon={icon_path}
_("Error"),
_("Failed to open folder: {error}").format(error=str(e))
)
+
+class CustomLineEdit(QLineEdit):
+
+ def __init__(self, *args, theme=None, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.theme = theme
+ self.theme_manager = ThemeManager()
+
+ def contextMenuEvent(self, event):
+ def get_safe_icon(icon_name: str) -> QIcon:
+ icon = self.theme_manager.get_icon(icon_name)
+ if isinstance(icon, QIcon):
+ return icon
+ elif isinstance(icon, str) and os.path.exists(icon):
+ return QIcon(icon)
+ return QIcon()
+
+ menu = QMenu(self)
+ if self.theme and hasattr(self.theme, "CONTEXT_MENU_STYLE"):
+ menu.setStyleSheet(self.theme.CONTEXT_MENU_STYLE)
+
+ # Undo
+ undo = menu.addAction(get_safe_icon("undo"), _("Undo\tCtrl+Z"))
+ undo.triggered.connect(self.undo)
+ undo.setEnabled(self.isUndoAvailable())
+
+ # Redo
+ redo = menu.addAction(get_safe_icon("redo"), _("Redo\tCtrl+Y"))
+ redo.triggered.connect(self.redo)
+ redo.setEnabled(self.isRedoAvailable())
+
+ menu.addSeparator()
+
+ # Cut
+ cut = menu.addAction(get_safe_icon("cut"), _("Cut\tCtrl+X"))
+ cut.triggered.connect(self.cut)
+ cut.setEnabled(self.hasSelectedText())
+
+ # Copy
+ copy = menu.addAction(get_safe_icon("copy"), _("Copy\tCtrl+C"))
+ copy.triggered.connect(self.copy)
+ copy.setEnabled(self.hasSelectedText())
+
+ # Paste
+ paste = menu.addAction(get_safe_icon("paste"), _("Paste\tCtrl+V"))
+ paste.triggered.connect(self.paste)
+ paste.setEnabled(QApplication.clipboard().mimeData().hasText())
+
+ # Delete
+ delete = menu.addAction(get_safe_icon("delete"), _("Delete\tDel"))
+ delete.triggered.connect(self._delete_selected_text)
+ delete.setEnabled(self.hasSelectedText())
+
+ menu.addSeparator()
+
+ # Select All
+ select_all = menu.addAction(get_safe_icon("select_all"), _("Select All\tCtrl+A"))
+ select_all.triggered.connect(self.selectAll)
+ select_all.setEnabled(bool(self.text()))
+
+ menu.exec(event.globalPos())
+
+ def _delete_selected_text(self):
+ cursor_pos = self.cursorPosition()
+ self.backspace()
+ self.setCursorPosition(cursor_pos)
diff --git a/portprotonqt/main_window.py b/portprotonqt/main_window.py
index 16584d9..211132a 100644
--- a/portprotonqt/main_window.py
+++ b/portprotonqt/main_window.py
@@ -12,7 +12,7 @@ from portprotonqt.dialogs import AddGameDialog, FileExplorer
from portprotonqt.game_card import GameCard
from portprotonqt.custom_widgets import FlowLayout, ClickableLabel, AutoSizeButton, NavLabel
from portprotonqt.input_manager import InputManager
-from portprotonqt.context_menu_manager import ContextMenuManager
+from portprotonqt.context_menu_manager import ContextMenuManager, CustomLineEdit
from portprotonqt.system_overlay import SystemOverlay
from portprotonqt.image_utils import load_pixmap_async, round_corners, ImageCarousel
@@ -589,7 +589,7 @@ class MainWindow(QMainWindow):
self.addGameButton.clicked.connect(self.openAddGameDialog)
layout.addWidget(self.addGameButton, alignment=Qt.AlignmentFlag.AlignRight)
- self.searchEdit = QLineEdit()
+ self.searchEdit = CustomLineEdit(self, theme=self.theme)
icon: QIcon = cast(QIcon, self.theme_manager.get_icon("search"))
action_pos = cast(QLineEdit.ActionPosition, QLineEdit.ActionPosition.LeadingPosition)
self.search_action = self.searchEdit.addAction(icon, action_pos)
@@ -1039,7 +1039,7 @@ class MainWindow(QMainWindow):
formLayout.addRow(self.gamesDisplayTitle, self.gamesDisplayCombo)
# 4. Proxy settings
- self.proxyUrlEdit = QLineEdit()
+ self.proxyUrlEdit = CustomLineEdit(self, theme=self.theme)
self.proxyUrlEdit.setPlaceholderText(_("Proxy URL"))
self.proxyUrlEdit.setStyleSheet(self.theme.PROXY_INPUT_STYLE)
self.proxyUrlEdit.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
@@ -1051,7 +1051,7 @@ class MainWindow(QMainWindow):
self.proxyUrlEdit.setText(proxy_cfg["http"])
formLayout.addRow(self.proxyUrlTitle, self.proxyUrlEdit)
- self.proxyUserEdit = QLineEdit()
+ self.proxyUserEdit = CustomLineEdit(self, theme=self.theme)
self.proxyUserEdit.setPlaceholderText(_("Proxy Username"))
self.proxyUserEdit.setStyleSheet(self.theme.PROXY_INPUT_STYLE)
self.proxyUserEdit.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
@@ -1060,7 +1060,7 @@ class MainWindow(QMainWindow):
self.proxyUserTitle.setFocusPolicy(Qt.FocusPolicy.NoFocus)
formLayout.addRow(self.proxyUserTitle, self.proxyUserEdit)
- self.proxyPasswordEdit = QLineEdit()
+ self.proxyPasswordEdit = CustomLineEdit(self, theme=self.theme)
self.proxyPasswordEdit.setPlaceholderText(_("Proxy Password"))
self.proxyPasswordEdit.setEchoMode(QLineEdit.EchoMode.Password)
self.proxyPasswordEdit.setStyleSheet(self.theme.PROXY_INPUT_STYLE)
@@ -1359,7 +1359,7 @@ class MainWindow(QMainWindow):
self.themeMetainfoLabel.setWordWrap(True)
self.themeInfoLayout.addWidget(self.themeMetainfoLabel)
- self.applyButton = AutoSizeButton(_("Apply Theme"), icon=self.theme_manager.get_icon("update"))
+ self.applyButton = AutoSizeButton(_("Apply Theme"), icon=self.theme_manager.get_icon("apply"))
self.applyButton.setStyleSheet(self.theme.ACTION_BUTTON_STYLE)
self.applyButton.setObjectName("actionButton")
self.themeInfoLayout.addWidget(self.applyButton)
diff --git a/portprotonqt/themes/standart/images/icons/copy.svg b/portprotonqt/themes/standart/images/icons/copy.svg
new file mode 100644
index 0000000..3f23b76
--- /dev/null
+++ b/portprotonqt/themes/standart/images/icons/copy.svg
@@ -0,0 +1 @@
+
diff --git a/portprotonqt/themes/standart/images/icons/cut.svg b/portprotonqt/themes/standart/images/icons/cut.svg
new file mode 100644
index 0000000..55df7a9
--- /dev/null
+++ b/portprotonqt/themes/standart/images/icons/cut.svg
@@ -0,0 +1 @@
+
diff --git a/portprotonqt/themes/standart/images/icons/delete.svg b/portprotonqt/themes/standart/images/icons/delete.svg
index 0a7ef6d..0d44f54 100644
--- a/portprotonqt/themes/standart/images/icons/delete.svg
+++ b/portprotonqt/themes/standart/images/icons/delete.svg
@@ -1 +1 @@
-
+
diff --git a/portprotonqt/themes/standart/images/icons/paste.svg b/portprotonqt/themes/standart/images/icons/paste.svg
new file mode 100644
index 0000000..e15aea0
--- /dev/null
+++ b/portprotonqt/themes/standart/images/icons/paste.svg
@@ -0,0 +1 @@
+
diff --git a/portprotonqt/themes/standart/images/icons/redo.svg b/portprotonqt/themes/standart/images/icons/redo.svg
new file mode 100644
index 0000000..18110bc
--- /dev/null
+++ b/portprotonqt/themes/standart/images/icons/redo.svg
@@ -0,0 +1 @@
+
diff --git a/portprotonqt/themes/standart/images/icons/select_all.svg b/portprotonqt/themes/standart/images/icons/select_all.svg
new file mode 100644
index 0000000..fae609b
--- /dev/null
+++ b/portprotonqt/themes/standart/images/icons/select_all.svg
@@ -0,0 +1 @@
+
diff --git a/portprotonqt/themes/standart/images/icons/undo.svg b/portprotonqt/themes/standart/images/icons/undo.svg
new file mode 100644
index 0000000..706fd9d
--- /dev/null
+++ b/portprotonqt/themes/standart/images/icons/undo.svg
@@ -0,0 +1 @@
+
diff --git a/portprotonqt/themes/standart/styles.py b/portprotonqt/themes/standart/styles.py
index d885cf5..a103820 100644
--- a/portprotonqt/themes/standart/styles.py
+++ b/portprotonqt/themes/standart/styles.py
@@ -103,9 +103,10 @@ CONTEXT_MENU_STYLE = f"""
font-family: '{font_family}';
font-size: {font_size_a};
padding: 5px;
+ min-width: 150px;
}}
QMenu::icon {{
- margin-left: 15px;
+ margin-left: 15px;
}}
QMenu::item {{
padding: 8px 20px 8px 10px;
@@ -117,6 +118,9 @@ CONTEXT_MENU_STYLE = f"""
background: {color_a};
color: {color_f};
}}
+ QMenu::item:disabled {{
+ color: #7f7f7f;
+ }}
QMenu::item:hover {{
background: {color_a};
color: {color_f};
@@ -127,6 +131,11 @@ CONTEXT_MENU_STYLE = f"""
border: {border_b} rgba(255, 255, 255, 0.3);
border-radius: {border_radius_a};
}}
+ QMenu::separator {{
+ height: 1px;
+ background-color: #7f7f7f;
+ margin: 4px 8px;
+ }}
"""
# ГЛОБАЛЬНЫЙ СТИЛЬ ДЛЯ ОКНА (ФОН), ЛЭЙБЛОВ, КНОПОК
@@ -742,19 +751,6 @@ PROXY_INPUT_STYLE = f"""
border: {border_c} {color_a};
background-color: {color_e};
}}
- QMenu {{
- border: {border_b} rgba(255, 255, 255, 0.2);
- padding: 5px 10px;
- background: {color_d};
- }}
- QMenu::item {{
- padding: 0px 10px;
- border: 10px solid {color_h}; /* reserve space for selection border */
- }}
- QMenu::item:selected {{
- background: {color_c};
- border-radius: {border_radius_a};
- }}
"""
SETTINGS_COMBO_STYLE = f"""