feat(context_menu_manager): add class CustomLineEdit

This commit is contained in:
2025-07-02 21:25:53 +07:00
parent 14b5d6ce6f
commit 80089a81d5
10 changed files with 90 additions and 22 deletions

View File

@ -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)

View File

@ -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)

View File

@ -0,0 +1 @@
<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="m6.25 12.2q-0.5775 0-0.98875-0.41125t-0.41125-0.98875v-8.4q0-0.5775 0.41125-0.98875t0.98875-0.41125h6.3q0.5775 0 0.98875 0.41125t0.41125 0.98875v8.4q0 0.5775-0.41125 0.98875t-0.98875 0.41125zm0-1.4h6.3v-8.4h-6.3zm-2.8 4.2q-0.5775 0-0.98875-0.41125t-0.41125-0.98875v-9.8h1.4v9.8h7.7v1.4zm2.8-4.2v-8.4z" fill="#fff" stroke-width=".0175"/></svg>

After

Width:  |  Height:  |  Size: 470 B

View File

@ -0,0 +1 @@
<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="m14.3 3.1-4.9 4.9 1.645 1.645q0.2625-0.14 0.56-0.1925t0.595-0.0525q1.155 0 1.9775 0.8225t0.8225 1.9775-0.8225 1.9775-1.9775 0.8225-1.9775-0.8225-0.8225-1.9775q0-0.2975 0.0525-0.595t0.1925-0.56l-1.645-1.645-1.645 1.645q0.14 0.2625 0.1925 0.56t0.0525 0.595q0 1.155-0.8225 1.9775t-1.9775 0.8225-1.9775-0.8225-0.8225-1.9775 0.8225-1.9775 1.9775-0.8225q0.2975 0 0.595 0.0525t0.56 0.1925l8.645-8.645h0.7zm-7 2.8-1.4 1.4-4.2-4.2v-2.1h0.7zm-2.1 6.3q0-0.5775-0.41125-0.98875t-0.98875-0.41125-0.98875 0.41125-0.41125 0.98875 0.41125 0.98875 0.98875 0.41125 0.98875-0.41125 0.41125-0.98875zm3.15-4.2q0-0.14-0.105-0.245t-0.245-0.105-0.245 0.105-0.105 0.245 0.105 0.245 0.245 0.105 0.245-0.105 0.105-0.245zm5.25 4.2q0-0.5775-0.41125-0.98875t-0.98875-0.41125-0.98875 0.41125-0.41125 0.98875 0.41125 0.98875 0.98875 0.41125 0.98875-0.41125 0.41125-0.98875z" fill="#fff" stroke-width=".0175"/></svg>

After

Width:  |  Height:  |  Size: 1011 B

View File

@ -1 +1 @@
<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="m5.48 11.5 2.52-2.52 2.52 2.52 0.98-0.98-2.52-2.52 2.52-2.52-0.98-0.98-2.52 2.52-2.52-2.52-0.98 0.98 2.52 2.52-2.52 2.52zm2.52 3.5q-1.4525 0-2.73-0.55125t-2.2225-1.4962-1.4962-2.2225-0.55125-2.73 0.55125-2.73 1.4962-2.2225 2.2225-1.4962 2.73-0.55125 2.73 0.55125 2.2225 1.4962 1.4962 2.2225 0.55125 2.73-0.55125 2.73-1.4962 2.2225-2.2225 1.4962-2.73 0.55125zm0-1.4q2.345 0 3.9725-1.6275t1.6275-3.9725-1.6275-3.9725-3.9725-1.6275-3.9725 1.6275-1.6275 3.9725 1.6275 3.9725 3.9725 1.6275z" fill="#fff"/></svg>
<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="m4.1111 15q-0.64167 0-1.0986-0.45694-0.45694-0.45694-0.45694-1.0986v-10.111h-0.77778v-1.5556h3.8889v-0.77778h4.6667v0.77778h3.8889v1.5556h-0.77778v10.111q0 0.64167-0.45694 1.0986-0.45694 0.45694-1.0986 0.45694zm7.7778-11.667h-7.7778v10.111h7.7778zm-6.2222 8.5556h1.5556v-7h-1.5556zm3.1111 0h1.5556v-7h-1.5556zm-4.6667-8.5556v10.111z" fill="#fff" stroke-width=".019444"/></svg>

Before

Width:  |  Height:  |  Size: 634 B

After

Width:  |  Height:  |  Size: 504 B

View File

@ -0,0 +1 @@
<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="m3.1 15q-0.5775 0-0.98875-0.41125t-0.41125-0.98875v-9.8q0-0.5775 0.41125-0.98875t0.98875-0.41125h2.9225q0.1925-0.6125 0.7525-1.0062t1.225-0.39375q0.7 0 1.2512 0.39375t0.74375 1.0062h2.905q0.5775 0 0.98875 0.41125t0.41125 0.98875v9.8q0 0.5775-0.41125 0.98875t-0.98875 0.41125zm0-1.4h9.8v-9.8h-1.4v2.1h-7v-2.1h-1.4zm4.9-9.8q0.2975 0 0.49875-0.20125t0.20125-0.49875-0.20125-0.49875-0.49875-0.20125-0.49875 0.20125-0.20125 0.49875 0.20125 0.49875 0.49875 0.20125z" fill="#fff" stroke-width=".0175"/></svg>

After

Width:  |  Height:  |  Size: 629 B

View File

@ -0,0 +1 @@
<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="m6.1625 14.562q-2.1219 0-3.6422-1.3781t-1.5203-3.4344 1.5203-3.4344 3.6422-1.3781h5.5125l-2.275-2.275 1.225-1.225 4.375 4.375-4.375 4.375-1.225-1.225 2.275-2.275h-5.5125q-1.3781 0-2.3953 0.875t-1.0172 2.1875 1.0172 2.1875 2.3953 0.875h6.2125v1.75z" fill="#fff" stroke-width=".021875"/></svg>

After

Width:  |  Height:  |  Size: 419 B

View File

@ -0,0 +1 @@
<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="m4.1111 11.889v-7.7778h7.7778v7.7778zm1.5556-1.5556h4.6667v-4.6667h-4.6667zm-3.1111 3.1111v1.5556q-0.64167 0-1.0986-0.45694-0.45694-0.45694-0.45694-1.0986zm-1.5556-1.5556v-1.5556h1.5556v1.5556zm0-3.1111v-1.5556h1.5556v1.5556zm0-3.1111v-1.5556h1.5556v1.5556zm1.5556-3.1111h-1.5556q0-0.64167 0.45694-1.0986 0.45694-0.45694 1.0986-0.45694zm1.5556 12.444v-1.5556h1.5556v1.5556zm0-12.444v-1.5556h1.5556v1.5556zm3.1111 12.444v-1.5556h1.5556v1.5556zm0-12.444v-1.5556h1.5556v1.5556zm3.1111 12.444v-1.5556h1.5556v1.5556zm0-12.444v-1.5556h1.5556v1.5556zm3.1111 12.444v-1.5556h1.5556q0 0.64167-0.45694 1.0986-0.45694 0.45694-1.0986 0.45694zm0-3.1111v-1.5556h1.5556v1.5556zm0-3.1111v-1.5556h1.5556v1.5556zm0-3.1111v-1.5556h1.5556v1.5556zm0-3.1111v-1.5556q0.64167 0 1.0986 0.45694 0.45694 0.45694 0.45694 1.0986z" fill="#fff" stroke-width=".019444"/></svg>

After

Width:  |  Height:  |  Size: 971 B

View File

@ -0,0 +1 @@
<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="m3.625 14.562v-1.75h6.2125q1.3781 0 2.3953-0.875 1.0172-0.875 1.0172-2.1875t-1.0172-2.1875q-1.0172-0.875-2.3953-0.875h-5.5125l2.275 2.275-1.225 1.225-4.375-4.375 4.375-4.375 1.225 1.225-2.275 2.275h5.5125q2.1219 0 3.6422 1.3781 1.5203 1.3781 1.5203 3.4344t-1.5203 3.4344q-1.5203 1.3781-3.6422 1.3781z" fill="#fff" stroke-width=".021875"/></svg>

After

Width:  |  Height:  |  Size: 472 B

View File

@ -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"""