feat(context_menu_manager): add icons to context menu
Some checks failed
Code and build check / Check code (pull_request) Failing after 1m30s
Code and build check / Build with uv (pull_request) Successful in 1m0s

This commit is contained in:
2025-07-01 21:20:42 +07:00
parent d7ab7dc7ce
commit fa0bde01e5
7 changed files with 40 additions and 28 deletions

View File

@ -8,12 +8,13 @@ import logging
import orjson import orjson
from PySide6.QtWidgets import QMessageBox, QDialog, QMenu from PySide6.QtWidgets import QMessageBox, QDialog, QMenu
from PySide6.QtCore import QUrl, QPoint, QObject, Signal, Qt from PySide6.QtCore import QUrl, QPoint, QObject, Signal, Qt
from PySide6.QtGui import QDesktopServices from PySide6.QtGui import QDesktopServices, QIcon
from portprotonqt.localization import _ from portprotonqt.localization import _
from portprotonqt.config_utils import parse_desktop_entry, read_favorites, save_favorites from portprotonqt.config_utils import parse_desktop_entry, read_favorites, save_favorites
from portprotonqt.steam_api import is_game_in_steam, add_to_steam, remove_from_steam from portprotonqt.steam_api import is_game_in_steam, add_to_steam, remove_from_steam
from portprotonqt.egs_api import add_egs_to_steam, get_egs_executable, remove_egs_from_steam from portprotonqt.egs_api import add_egs_to_steam, get_egs_executable, remove_egs_from_steam
from portprotonqt.dialogs import AddGameDialog, FileExplorer, generate_thumbnail from portprotonqt.dialogs import AddGameDialog, FileExplorer, generate_thumbnail
from portprotonqt.theme_manager import ThemeManager
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -40,6 +41,7 @@ class ContextMenuManager:
self.parent = parent self.parent = parent
self.portproton_location = portproton_location self.portproton_location = portproton_location
self.theme = theme self.theme = theme
self.theme_manager = ThemeManager()
self.load_games = load_games_callback self.load_games = load_games_callback
self.update_game_grid = update_game_grid_callback self.update_game_grid = update_game_grid_callback
self.legendary_path = os.path.join( self.legendary_path = os.path.join(
@ -129,16 +131,16 @@ class ContextMenuManager:
menu = QMenu(self.parent) menu = QMenu(self.parent)
menu.setStyleSheet(self.theme.CONTEXT_MENU_STYLE) menu.setStyleSheet(self.theme.CONTEXT_MENU_STYLE)
launch_action = menu.addAction(_("Launch Game")) launch_action = menu.addAction(QIcon(self.theme_manager.get_icon("play")), _("Launch Game"))
launch_action.triggered.connect( launch_action.triggered.connect(
lambda: self._launch_game(game_card) lambda: self._launch_game(game_card)
) )
favorites = read_favorites() favorites = read_favorites()
is_favorite = game_card.name in favorites is_favorite = game_card.name in favorites
favorite_action = menu.addAction( icon_name = "star_full" if is_favorite else "star"
_("Remove from Favorites") if is_favorite else _("Add to Favorites") text = _("Remove from Favorites") if is_favorite else _("Add to Favorites")
) favorite_action = menu.addAction(QIcon(self.theme_manager.get_icon(icon_name)), text)
favorite_action.triggered.connect(lambda: self.toggle_favorite(game_card, not is_favorite)) favorite_action.triggered.connect(lambda: self.toggle_favorite(game_card, not is_favorite))
if game_card.game_source == "epic": if game_card.game_source == "epic":
@ -148,23 +150,23 @@ class ContextMenuManager:
) )
if self._is_egs_game_installed(game_card.appid): if self._is_egs_game_installed(game_card.appid):
is_in_steam = is_game_in_steam(game_card.name) is_in_steam = is_game_in_steam(game_card.name)
steam_action = menu.addAction( icon_name = "delete" if is_in_steam else "steam"
_("Remove from Steam") if is_in_steam else _("Add to Steam") text = _("Remove from Steam") if is_in_steam else _("Add to Steam")
) steam_action = menu.addAction(QIcon(self.theme_manager.get_icon(icon_name)), text)
steam_action.triggered.connect( steam_action.triggered.connect(
lambda: self.remove_from_steam(game_card.name, game_card.exec_line, game_card.game_source) lambda: self.remove_from_steam(game_card.name, game_card.exec_line, game_card.game_source)
if is_in_steam if is_in_steam
else self.add_egs_to_steam(game_card.name, game_card.appid) else self.add_egs_to_steam(game_card.name, game_card.appid)
) )
open_folder_action = menu.addAction(_("Open Game Folder")) open_folder_action = menu.addAction(QIcon(self.theme_manager.get_icon("search")), _("Open Game Folder"))
open_folder_action.triggered.connect( open_folder_action.triggered.connect(
lambda: self.open_egs_game_folder(game_card.appid) lambda: self.open_egs_game_folder(game_card.appid)
) )
desktop_dir = subprocess.check_output(['xdg-user-dir', 'DESKTOP']).decode('utf-8').strip() desktop_dir = subprocess.check_output(['xdg-user-dir', 'DESKTOP']).decode('utf-8').strip()
desktop_path = os.path.join(desktop_dir, f"{game_card.name}.desktop") desktop_path = os.path.join(desktop_dir, f"{game_card.name}.desktop")
desktop_action = menu.addAction( icon_name = "delete" if os.path.exists(desktop_path) else "desktop"
_("Remove from Desktop") if os.path.exists(desktop_path) else _("Add to Desktop") text = _("Remove from Desktop") if os.path.exists(desktop_path) else _("Add to Desktop")
) desktop_action = menu.addAction(QIcon(self.theme_manager.get_icon(icon_name)), text)
desktop_action.triggered.connect( desktop_action.triggered.connect(
lambda: self.remove_egs_from_desktop(game_card.name) lambda: self.remove_egs_from_desktop(game_card.name)
if os.path.exists(desktop_path) if os.path.exists(desktop_path)
@ -184,43 +186,45 @@ class ContextMenuManager:
if game_card.game_source not in ("steam", "epic"): if game_card.game_source not in ("steam", "epic"):
desktop_dir = subprocess.check_output(['xdg-user-dir', 'DESKTOP']).decode('utf-8').strip() desktop_dir = subprocess.check_output(['xdg-user-dir', 'DESKTOP']).decode('utf-8').strip()
desktop_path = os.path.join(desktop_dir, f"{game_card.name}.desktop") desktop_path = os.path.join(desktop_dir, f"{game_card.name}.desktop")
desktop_action = menu.addAction( icon_name = "delete" if os.path.exists(desktop_path) else "desktop"
_("Remove from Desktop") if os.path.exists(desktop_path) else _("Add to Desktop") text = _("Remove from Desktop") if os.path.exists(desktop_path) else _("Add to Desktop")
) desktop_action = menu.addAction(QIcon(self.theme_manager.get_icon(icon_name)), text)
desktop_action.triggered.connect( desktop_action.triggered.connect(
lambda: self.remove_from_desktop(game_card.name) lambda: self.remove_from_desktop(game_card.name)
if os.path.exists(desktop_path) if os.path.exists(desktop_path)
else self.add_to_desktop(game_card.name, game_card.exec_line) else self.add_to_desktop(game_card.name, game_card.exec_line)
) )
edit_action = menu.addAction(_("Edit Shortcut")) edit_action = menu.addAction(QIcon(self.theme_manager.get_icon("edit")), _("Edit Shortcut"))
edit_action.triggered.connect( edit_action.triggered.connect(
lambda: self.edit_game_shortcut(game_card.name, game_card.exec_line, game_card.cover_path) lambda: self.edit_game_shortcut(game_card.name, game_card.exec_line, game_card.cover_path)
) )
delete_action = menu.addAction(_("Delete from PortProton")) delete_action = menu.addAction(QIcon(self.theme_manager.get_icon("delete")), _("Delete from PortProton"))
delete_action.triggered.connect(lambda: self.delete_game(game_card.name, game_card.exec_line)) delete_action.triggered.connect(lambda: self.delete_game(game_card.name, game_card.exec_line))
open_folder_action = menu.addAction(_("Open Game Folder")) open_folder_action = menu.addAction(QIcon(self.theme_manager.get_icon("search")), _("Open Game Folder"))
open_folder_action.triggered.connect( open_folder_action.triggered.connect(
lambda: self.open_game_folder(game_card.name, game_card.exec_line) lambda: self.open_game_folder(game_card.name, game_card.exec_line)
) )
applications_dir = os.path.join(os.path.expanduser("~"), ".local", "share", "applications") applications_dir = os.path.join(os.path.expanduser("~"), ".local", "share", "applications")
menu_path = os.path.join(applications_dir, f"{game_card.name}.desktop") menu_path = os.path.join(applications_dir, f"{game_card.name}.desktop")
menu_action = menu.addAction( icon_name = "delete" if os.path.exists(menu_path) else "menu"
_("Remove from Menu") if os.path.exists(menu_path) else _("Add to Menu") text = _("Remove from Menu") if os.path.exists(menu_path) else _("Add to Menu")
) menu_action = menu.addAction(QIcon(self.theme_manager.get_icon(icon_name)), text)
menu_action.triggered.connect( menu_action.triggered.connect(
lambda: self.remove_from_menu(game_card.name) lambda: self.remove_from_menu(game_card.name)
if os.path.exists(menu_path) if os.path.exists(menu_path)
else self.add_to_menu(game_card.name, game_card.exec_line) else self.add_to_menu(game_card.name, game_card.exec_line)
) )
is_in_steam = is_game_in_steam(game_card.name) is_in_steam = is_game_in_steam(game_card.name)
steam_action = menu.addAction( icon_name = "delete" if is_in_steam else "steam"
_("Remove from Steam") if is_in_steam else _("Add to Steam") text = _("Remove from Steam") if is_in_steam else _("Add to Steam")
) steam_action = menu.addAction(QIcon(self.theme_manager.get_icon(icon_name)), text)
steam_action.triggered.connect( steam_action.triggered.connect(
lambda: self.remove_from_steam(game_card.name, game_card.exec_line, game_card.game_source) lambda: (
self.remove_from_steam(game_card.name, game_card.exec_line, game_card.game_source)
if is_in_steam if is_in_steam
else self.add_to_steam(game_card.name, game_card.exec_line, game_card.cover_path) else self.add_to_steam(game_card.name, game_card.exec_line, game_card.cover_path)
) )
)
menu.exec(game_card.mapToGlobal(pos)) menu.exec(game_card.mapToGlobal(pos))

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

After

Width:  |  Height:  |  Size: 634 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.0723 1c-1.6982 0-3.0801 1.3818-3.0801 3.0801v7.8398c0 1.6982 1.3818 3.0801 3.0801 3.0801h7.8398c1.6982 0 3.0801-1.3818 3.0801-3.0801v-7.8398l-0.93359 0.93359v6.9063c0 1.1973-0.94916 2.1465-2.1465 2.1465h-7.8398c-1.1973 0-2.1465-0.94916-2.1465-2.1465v-7.8398c0-1.1973 0.94916-2.1465 2.1465-2.1465h7.0938l0.85547-0.85352 0.0078-0.00781c0.02511-0.02302 0.04987-0.044899 0.07617-0.066406-0.064467-0.0040524-0.12786-0.0058621-0.19334-0.0058621zm9.1211 0.042969c-0.14335 0-0.28025 0.028276-0.41016 0.082031-0.12991 0.053755-0.24991 0.12997-0.35742 0.22852l-7.0937 7.082v2.2832h2.2832l7.082-7.082c0.10751-0.10751 0.18705-0.22556 0.23633-0.35547 0.04927-0.12991 0.07422-0.26681 0.07422-0.41016 0-0.13439-0.02495-0.26796-0.07422-0.40234-0.049276-0.13439-0.12882-0.25106-0.23633-0.34961l-0.73828-0.75391c-0.09855-0.10751-0.21522-0.18648-0.34961-0.24023-0.13439-0.053755-0.27267-0.082031-0.41602-0.082031zm-1.5332 2.5801 0.37695 0.39062 0.38867 0.37695-5.2539 5.2539h-0.76562v-0.76562z" fill="#fff" fill-rule="evenodd" stop-color="#000000" stroke-width=".93333"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

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="m1 1.75v2h3.5v-2zm5.25 0v2h8.75v-2zm-5.25 5.25v2h3.5v-2zm5.25 0v2h8.75v-2zm-5.25 5.25v2h3.5v-2zm5.25 0v2h8.75v-2z" fill="#fff" stroke-width=".75593"/></svg>

After

Width:  |  Height:  |  Size: 284 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="M 5.795,11.7275 8,10.3975 10.205,11.745 9.6275,9.225 11.57,7.545 9.015,7.3175 8,4.9375 6.985,7.3 4.43,7.5275 6.3725,9.225 Z M 3.6775,14.65 4.815,9.7325 1,6.425 6.04,5.9875 8,1.35 9.96,5.9875 15,6.425 11.185,9.7325 12.3225,14.65 8,12.0425 Z M 8,8.525 Z" fill="#fff" stroke-width=".0175"/></svg>

After

Width:  |  Height:  |  Size: 421 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="m8 1.3496-1.9609 4.6387-5.0391 0.4375 3.8145 3.3066-1.1367 4.918 4.3223-2.6074 4.3223 2.6074-1.1367-4.918 3.8145-3.3066-5.0391-0.4375z" fill="#fff" stroke-width=".0175"/></svg>

After

Width:  |  Height:  |  Size: 304 B

View File

@ -104,8 +104,11 @@ CONTEXT_MENU_STYLE = f"""
font-size: {font_size_a}; font-size: {font_size_a};
padding: 5px; padding: 5px;
}} }}
QMenu::icon {{
margin-left: 15px;
}}
QMenu::item {{ QMenu::item {{
padding: 8px 20px; padding: 8px 20px 8px 10px;
background: {color_h}; background: {color_h};
border-radius: {border_radius_a}; border-radius: {border_radius_a};
color: {color_f}; color: {color_f};