|
2 | 2 |
|
3 | 3 | import contextlib
|
4 | 4 | from typing import TYPE_CHECKING, ClassVar, Mapping
|
| 5 | +from weakref import WeakValueDictionary |
5 | 6 |
|
6 | 7 | from app_model import Application
|
7 | 8 | from app_model.expressions import Expr
|
|
13 | 14 | if TYPE_CHECKING:
|
14 | 15 | from PyQt6.QtGui import QAction
|
15 | 16 | from qtpy.QtCore import QObject
|
| 17 | + from typing_extensions import Self |
16 | 18 |
|
17 | 19 | from app_model.types import CommandRule, MenuItem
|
18 | 20 | else:
|
@@ -118,52 +120,64 @@ class QMenuItemAction(QCommandRuleAction):
|
118 | 120 |
|
119 | 121 | Mostly the same as a `CommandRuleAction`, but aware of the `menu_item.when` clause
|
120 | 122 | to toggle visibility.
|
121 |
| - """ |
122 |
| - |
123 |
| - _cache: ClassVar[dict[tuple[int, int], QMenuItemAction]] = {} |
124 | 123 |
|
125 |
| - def __new__( |
126 |
| - cls: type[QMenuItemAction], |
127 |
| - menu_item: MenuItem, |
128 |
| - app: Application | str, |
129 |
| - parent: QObject | None = None, |
130 |
| - *, |
131 |
| - cache: bool = True, |
132 |
| - ) -> QMenuItemAction: |
133 |
| - """Create and cache a QMenuItemAction for the given menu item.""" |
134 |
| - app = Application.get_or_create(app) if isinstance(app, str) else app |
135 |
| - key = (id(app), hash(menu_item)) |
136 |
| - if cache and key in cls._cache: |
137 |
| - return cls._cache[key] |
| 124 | + Parameters |
| 125 | + ---------- |
| 126 | + menu_item : MenuItem |
| 127 | + `MenuItem` instance to create an action for. |
| 128 | + app : Application | str |
| 129 | + Application instance or name of application instance. |
| 130 | + parent : QObject | None |
| 131 | + Optional parent widget, by default None |
| 132 | + """ |
138 | 133 |
|
139 |
| - self = super().__new__(cls) |
140 |
| - if cache: |
141 |
| - cls._cache[key] = self |
142 |
| - return self # type: ignore [no-any-return] |
| 134 | + _cache: ClassVar[WeakValueDictionary[tuple[int, int], QMenuItemAction]] = ( |
| 135 | + WeakValueDictionary() |
| 136 | + ) |
143 | 137 |
|
144 | 138 | def __init__(
|
145 | 139 | self,
|
146 | 140 | menu_item: MenuItem,
|
147 | 141 | app: Application | str,
|
148 | 142 | parent: QObject | None = None,
|
149 |
| - *, |
150 |
| - cache: bool = True, # used in __new__ |
151 | 143 | ):
|
152 |
| - initialized = False |
153 |
| - with contextlib.suppress(RuntimeError): |
154 |
| - initialized = getattr(self, "_initialized", False) |
155 |
| - |
156 |
| - if not initialized: |
157 |
| - super().__init__(menu_item.command, app, parent) |
158 |
| - self._menu_item = menu_item |
159 |
| - key = (id(self._app), hash(menu_item)) |
160 |
| - self.destroyed.connect(lambda: QMenuItemAction._cache.pop(key, None)) |
161 |
| - self._app.destroyed.connect(lambda: QMenuItemAction._cache.pop(key, None)) |
162 |
| - self._initialized = True |
163 |
| - |
| 144 | + super().__init__(menu_item.command, app, parent) |
| 145 | + self._menu_item = menu_item |
164 | 146 | with contextlib.suppress(NameError):
|
165 | 147 | self.update_from_context(self._app.context)
|
166 | 148 |
|
| 149 | + @staticmethod |
| 150 | + def _cache_key(app: Application, menu_item: MenuItem) -> tuple[int, int]: |
| 151 | + return (id(app), hash(menu_item)) |
| 152 | + |
| 153 | + @classmethod |
| 154 | + def create( |
| 155 | + cls, |
| 156 | + menu_item: MenuItem, |
| 157 | + app: Application | str, |
| 158 | + parent: QObject | None = None, |
| 159 | + ) -> Self: |
| 160 | + """Create a new QMenuItemAction for the given menu item. |
| 161 | +
|
| 162 | + Prefer this method over `__init__` to ensure that the cache is used, |
| 163 | + so that: |
| 164 | +
|
| 165 | + ```python |
| 166 | + a1 = QMenuItemAction.create(action, full_app) |
| 167 | + a2 = QMenuItemAction.create(action, full_app) |
| 168 | + a1 is a2 # True |
| 169 | + ``` |
| 170 | + """ |
| 171 | + app = Application.get_or_create(app) if isinstance(app, str) else app |
| 172 | + cache_key = QMenuItemAction._cache_key(app, menu_item) |
| 173 | + if cache_key in cls._cache: |
| 174 | + res = cls._cache[cache_key] |
| 175 | + res.setParent(parent) |
| 176 | + return res |
| 177 | + |
| 178 | + cls._cache[cache_key] = obj = cls(menu_item, app, parent) |
| 179 | + return obj |
| 180 | + |
167 | 181 | def update_from_context(self, ctx: Mapping[str, object]) -> None:
|
168 | 182 | """Update the enabled/visible state of this menu item from `ctx`."""
|
169 | 183 | super().update_from_context(ctx)
|
|
0 commit comments