quickmacapp 2025.4.15__tar.gz → 2025.7.22__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quickmacapp
3
- Version: 2025.4.15
3
+ Version: 2025.7.22
4
4
  Summary: Make it easier to write Mac apps in Python
5
5
  Description-Content-Type: text/x-rst
6
6
  License-File: LICENSE
@@ -9,7 +9,7 @@ build-backend = "setuptools.build_meta"
9
9
  name = "quickmacapp"
10
10
  description = "Make it easier to write Mac apps in Python"
11
11
  readme = "README.rst"
12
- version = "2025.04.15"
12
+ version = "2025.07.22"
13
13
  dependencies = [
14
14
  "pyobjc-framework-Cocoa",
15
15
  "pyobjc-framework-ExceptionHandling",
@@ -1,9 +1,10 @@
1
- from ._quickapp import Actionable, Status, mainpoint, menu, quit
1
+ from ._quickapp import Actionable, ItemState, Status, mainpoint, menu, quit
2
2
  from ._interactions import ask, choose, answer, getpass
3
3
  from ._background import dockIconWhenVisible
4
4
 
5
5
  __all__ = [
6
6
  "Actionable",
7
+ "ItemState",
7
8
  "Status",
8
9
  "mainpoint",
9
10
  "menu",
@@ -3,31 +3,48 @@ from __future__ import annotations
3
3
  import os
4
4
  import sys
5
5
  import traceback
6
- from typing import Callable, Protocol, Any
7
-
8
- from objc import ivar, IBAction, super
9
-
10
- from Foundation import (
11
- NSObject,
12
- NSException,
13
- )
6
+ from dataclasses import dataclass
7
+ from types import FunctionType
8
+ from typing import Any, Callable, Iterable, Protocol, Sequence, Literal
14
9
 
15
10
  from AppKit import (
16
11
  NSApp,
17
12
  NSApplication,
18
13
  NSEvent,
19
- NSResponder,
20
- NSMenu,
21
14
  NSImage,
15
+ NSMenu,
22
16
  NSMenuItem,
17
+ NSResponder,
23
18
  NSStatusBar,
24
19
  NSVariableStatusItemLength,
20
+ NSControlStateValueOn,
21
+ NSControlStateValueOff,
25
22
  )
26
-
23
+ from ExceptionHandling import NSStackTraceKey # type:ignore
24
+ from Foundation import NSException, NSObject
25
+ from objc import IBAction, ivar, super
27
26
  from PyObjCTools.Debugging import _run_atos, isPythonException
28
- from ExceptionHandling import ( # type:ignore
29
- NSStackTraceKey,
30
- )
27
+
28
+
29
+ def asSelectorString(f: FunctionType) -> str:
30
+ """
31
+ Convert a method on a PyObjC class into a selector string.
32
+ """
33
+ return f.__name__.replace("_", ":")
34
+
35
+
36
+ @dataclass(kw_only=True)
37
+ class ItemState:
38
+ """
39
+ The state of a menu item.
40
+ """
41
+
42
+ enabled: bool = True
43
+ "Should the menu item be disabled? True if not, False if so."
44
+ checked: bool = False
45
+ "Should the menu item display a check-mark next to itself? True if so, False if not."
46
+ key: str | None = None
47
+ "Should the menu shortcut mnemonic key be set, blank, or derived from the item's title?"
31
48
 
32
49
 
33
50
  class Actionable(NSObject):
@@ -35,15 +52,29 @@ class Actionable(NSObject):
35
52
  Wrap a Python no-argument function call in an NSObject with a C{doIt:}
36
53
  method.
37
54
  """
38
- _thunk: Callable[[], None]
55
+
56
+ _thunk: Callable[[], object]
57
+ _state: ItemState
39
58
 
40
59
  def initWithFunction_(self, thunk: Callable[[], None]) -> Actionable:
41
60
  """
42
- Remember the given callable.
61
+ Backwards compatibility initializer, creating this L{Actionable} in the
62
+ default L{ItemState}.
63
+ """
64
+ return self.initWithFunction_andState_(thunk, ItemState())
65
+
66
+ def initWithFunction_andState_(
67
+ self, thunk: Callable[[], None], state: ItemState
68
+ ) -> Actionable:
69
+ """
70
+ Remember the given callable, and the given menu state.
43
71
 
44
72
  @param thunk: the callable to run in L{doIt_}.
73
+
74
+ @param state: the initial state of the menu item presentation
45
75
  """
46
76
  self._thunk = thunk
77
+ self._state = state
47
78
  return self
48
79
 
49
80
  @IBAction
@@ -52,10 +83,41 @@ class Actionable(NSObject):
52
83
  Call the given callable; exposed as an C{IBAction} in case you want IB
53
84
  to be able to see it.
54
85
  """
55
- self._thunk()
86
+ result = self._thunk()
87
+ if isinstance(result, ItemState):
88
+ self._state = result
89
+
90
+ def validateMenuItem_(self, item: NSMenuItem) -> bool:
91
+ item.setState_(
92
+ NSControlStateValueOn if self._state.checked else NSControlStateValueOff
93
+ )
94
+ return self._state.enabled
95
+
96
+
97
+ ACTION_METHOD = asSelectorString(Actionable.doIt_)
98
+
99
+
100
+ def _adjust(
101
+ items: Iterable[
102
+ tuple[str, Callable[[], object]] | tuple[str, Callable[[], object], ItemState]
103
+ ],
104
+ ) -> Iterable[tuple[str, Callable[[], object], ItemState]]:
105
+ for item in items:
106
+ if len(item) == 3:
107
+ yield item
108
+ else:
109
+ yield (*item, ItemState())
56
110
 
57
111
 
58
- def menu(title: str, items: list[tuple[str, Callable[[], object]]]) -> NSMenu:
112
+ ItemSeq = Sequence[
113
+ tuple[str, Callable[[], object]] | tuple[str, Callable[[], object], ItemState]
114
+ ]
115
+
116
+
117
+ def menu(
118
+ title: str,
119
+ items: ItemSeq,
120
+ ) -> NSMenu:
59
121
  """
60
122
  Construct an NSMenu from a list of tuples describing it.
61
123
 
@@ -68,11 +130,16 @@ def menu(title: str, items: list[tuple[str, Callable[[], object]]]) -> NSMenu:
68
130
  @return: a new Menu tha is not attached to anything.
69
131
  """
70
132
  result = NSMenu.alloc().initWithTitle_(title)
71
- for (subtitle, thunk) in items:
133
+ for subtitle, thunk, state in _adjust(items):
134
+ initialKeyEquivalent = subtitle[0].lower()
72
135
  item = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(
73
- subtitle, "doIt:", subtitle[0].lower()
136
+ subtitle,
137
+ ACTION_METHOD,
138
+ initialKeyEquivalent if state.key is None else state.key,
139
+ )
140
+ item.setTarget_(
141
+ Actionable.alloc().initWithFunction_andState_(thunk, state).retain()
74
142
  )
75
- item.setTarget_(Actionable.alloc().initWithFunction_(thunk).retain())
76
143
  result.addItem_(item)
77
144
  result.update()
78
145
  return result
@@ -97,11 +164,12 @@ class Status:
97
164
  self.item.button().setImage_(image)
98
165
  elif text is None:
99
166
  from __main__ import __file__ as default
167
+
100
168
  text = os.path.basename(default)
101
169
  if text is not None:
102
170
  self.item.button().setTitle_(text)
103
171
 
104
- def menu(self, items: list[tuple[str, Callable[[], object]]]) -> None:
172
+ def menu(self, items: ItemSeq) -> None:
105
173
  """
106
174
  Set the status drop-down menu.
107
175
 
@@ -183,6 +251,7 @@ class QuickApplication(NSApplication):
183
251
  be more complicated in LSUIElement apps, but there might be a better
184
252
  way to do this.)
185
253
  """
254
+
186
255
  keyEquivalentHandler: NSResponder = ivar()
187
256
 
188
257
  def sendEvent_(self, event: NSEvent) -> None:
@@ -210,6 +279,7 @@ class MainRunner(Protocol):
210
279
  """
211
280
  A function which has been decorated with a runMain attribute.
212
281
  """
282
+
213
283
  def __call__(self, reactor: Any) -> None:
214
284
  """
215
285
  @param reactor: A Twisted reactor, which provides the usual suspects of
@@ -218,6 +288,7 @@ class MainRunner(Protocol):
218
288
 
219
289
  runMain: Callable[[], None]
220
290
 
291
+
221
292
  def mainpoint() -> Callable[[Callable[[Any], None]], MainRunner]:
222
293
  """
223
294
  Add a .runMain attribute to function
@@ -227,10 +298,11 @@ def mainpoint() -> Callable[[Callable[[Any], None]], MainRunner]:
227
298
  The runMain attribute starts a reactor and calls the original function
228
299
  with a running, initialized, reactor.
229
300
  """
301
+
230
302
  def wrapup(appmain: Callable[[Any], None]) -> MainRunner:
231
303
  def doIt() -> None:
232
- from twisted.internet import cfreactor
233
304
  import PyObjCTools.AppHelper
305
+ from twisted.internet import cfreactor
234
306
 
235
307
  QuickApplication.sharedApplication()
236
308
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quickmacapp
3
- Version: 2025.4.15
3
+ Version: 2025.7.22
4
4
  Summary: Make it easier to write Mac apps in Python
5
5
  Description-Content-Type: text/x-rst
6
6
  License-File: LICENSE
File without changes