add class CustomLineEdit & new icons for context menu #22
| @@ -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""" | ||||
|   | ||||