feat(context_menu_manager): add class CustomLineEdit
@ -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)
|
||||
|
@ -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)
|
||||
|
1
portprotonqt/themes/standart/images/icons/copy.svg
Normal 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 |
1
portprotonqt/themes/standart/images/icons/cut.svg
Normal 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 |
@ -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 |
1
portprotonqt/themes/standart/images/icons/paste.svg
Normal 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 |
1
portprotonqt/themes/standart/images/icons/redo.svg
Normal 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 |
1
portprotonqt/themes/standart/images/icons/select_all.svg
Normal 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 |
1
portprotonqt/themes/standart/images/icons/undo.svg
Normal 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 |
@ -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"""
|
||||
|