0
# PyObjCTools Utilities
1
2
High-level utility modules that simplify common PyObjC development tasks. These tools provide Python-friendly wrappers for application lifecycle management, data conversion between Python and Objective-C, and enhanced functionality for existing Cocoa classes.
3
4
## Capabilities
5
6
### Application Lifecycle Management (PyObjCTools.AppHelper)
7
8
Essential functions for managing PyObjC application lifecycle, event loops, and cross-thread communication in macOS applications.
9
10
```python { .api }
11
def runEventLoop():
12
"""
13
Runs the main application event loop with enhanced exception handling.
14
15
Provides a safer alternative to NSApplicationMain() that catches and logs
16
Python exceptions that would otherwise crash the application.
17
18
Raises:
19
SystemExit: When application termination is requested
20
"""
21
22
def runConsoleEventLoop():
23
"""
24
Runs a console-friendly event loop that can be stopped programmatically.
25
26
Unlike runEventLoop(), this doesn't start a full GUI application but
27
allows processing of events and timers in command-line applications.
28
"""
29
30
def stopEventLoop():
31
"""
32
Stops the current event loop or terminates the application.
33
34
Can be called from any thread to cleanly shut down the application
35
or stop a console event loop started with runConsoleEventLoop().
36
"""
37
38
def endSheetMethod(method):
39
"""
40
Decorator that properly signs a method for use as a sheet callback.
41
42
Args:
43
method: Method to be used as sheet end callback
44
45
Returns:
46
Decorated method with correct Objective-C signature
47
48
Example:
49
@endSheetMethod
50
def sheetDidEnd_returnCode_contextInfo_(self, sheet, returnCode, contextInfo):
51
# Handle sheet completion
52
pass
53
"""
54
55
def callAfter(func, *args, **kwargs):
56
"""
57
Schedules a function to be called on the main thread asynchronously.
58
59
Args:
60
func: Function to call
61
*args: Positional arguments for function
62
**kwargs: Keyword arguments for function
63
64
Returns:
65
None (executes asynchronously)
66
"""
67
68
def callLater(delay, func, *args, **kwargs):
69
"""
70
Schedules a function to be called on the main thread after a delay.
71
72
Args:
73
delay (float): Delay in seconds before calling function
74
func: Function to call
75
*args: Positional arguments for function
76
**kwargs: Keyword arguments for function
77
78
Returns:
79
None (executes asynchronously)
80
"""
81
```
82
83
### Data Conversion Utilities (PyObjCTools.Conversion)
84
85
Functions for converting data between Python and Objective-C types, particularly useful for property lists and decimal numbers.
86
87
```python { .api }
88
def pythonCollectionFromPropertyList(collection):
89
"""
90
Converts Objective-C property list collections to Python equivalents.
91
92
Recursively converts NSArray, NSDictionary, NSString, NSNumber, NSDate,
93
and NSData objects to Python list, dict, str, int/float, datetime, and bytes.
94
95
Args:
96
collection: Objective-C collection object (NSArray, NSDictionary, etc.)
97
98
Returns:
99
Python equivalent collection (list, dict, etc.)
100
"""
101
102
def propertyListFromPythonCollection(collection):
103
"""
104
Converts Python collections to Objective-C property list objects.
105
106
Recursively converts Python list, dict, str, int, float, bool, datetime,
107
and bytes objects to NSArray, NSDictionary, NSString, NSNumber, NSDate, and NSData.
108
109
Args:
110
collection: Python collection (list, dict, etc.)
111
112
Returns:
113
Objective-C property list object
114
"""
115
116
def serializePropertyList(plist, format):
117
"""
118
Serializes a property list object to NSData.
119
120
Args:
121
plist: Property list object (NSDictionary, NSArray, etc.)
122
format: Serialization format (NSPropertyListXMLFormat_v1_0,
123
NSPropertyListBinaryFormat_v1_0, etc.)
124
125
Returns:
126
NSData: Serialized property list data
127
128
Raises:
129
ValueError: If serialization fails
130
"""
131
132
def deserializePropertyList(data):
133
"""
134
Deserializes property list data to objects.
135
136
Args:
137
data: NSData containing serialized property list
138
139
Returns:
140
tuple: (property_list_object, format_used)
141
142
Raises:
143
ValueError: If deserialization fails
144
"""
145
146
def toPythonDecimal(decimal):
147
"""
148
Converts NSDecimalNumber to Python decimal.Decimal.
149
150
Args:
151
decimal: NSDecimalNumber object
152
153
Returns:
154
decimal.Decimal: High-precision decimal number
155
"""
156
157
def fromPythonDecimal(decimal):
158
"""
159
Converts Python decimal.Decimal to NSDecimalNumber.
160
161
Args:
162
decimal: decimal.Decimal object
163
164
Returns:
165
NSDecimalNumber: Objective-C decimal number
166
"""
167
```
168
169
### Conversion Constants
170
171
```python { .api }
172
PYTHON_TYPES: tuple # Tuple of Python types supported for conversion
173
FORMATS: dict # Dictionary mapping format names to NSPropertyList format constants
174
```
175
176
### Class Enhancement Modules
177
178
Modules that automatically enhance existing Cocoa classes with Python-friendly methods when imported.
179
180
#### AppKit Enhancements (PyObjCTools.AppCategories)
181
182
```python { .api }
183
# NSGraphicsContext enhancements (automatically applied when imported)
184
@classmethod
185
def savedGraphicsState(cls):
186
"""
187
Context manager for preserving and restoring graphics state.
188
189
Returns:
190
Context manager that saves current graphics state on entry
191
and restores it on exit
192
193
Usage:
194
with NSGraphicsContext.savedGraphicsState():
195
# Modify graphics state
196
NSColor.redColor().set()
197
# State automatically restored on exit
198
"""
199
200
# NSAnimationContext enhancements (automatically applied when imported)
201
def __enter__(self):
202
"""Context manager entry for animation grouping."""
203
204
def __exit__(self, exc_type, exc_val, exc_tb):
205
"""Context manager exit for animation grouping."""
206
```
207
208
#### Foundation Enhancements (PyObjCTools.FndCategories)
209
210
```python { .api }
211
# NSAffineTransform enhancements (automatically applied when imported)
212
def rotateByDegrees_atPoint_(self, degrees, point):
213
"""
214
Rotates the coordinate space by degrees around a specific point.
215
216
Args:
217
degrees (float): Rotation angle in degrees
218
point: NSPoint around which to rotate
219
"""
220
221
def rotateByRadians_atPoint_(self, radians, point):
222
"""
223
Rotates the coordinate space by radians around a specific point.
224
225
Args:
226
radians (float): Rotation angle in radians
227
point: NSPoint around which to rotate
228
"""
229
```
230
231
## Usage Examples
232
233
### Application Lifecycle Management
234
235
```python
236
from PyObjCTools import AppHelper
237
import AppKit
238
239
class MyAppDelegate(AppKit.NSObject):
240
def applicationDidFinishLaunching_(self, notification):
241
print("Application started")
242
243
# Schedule something to run later
244
AppHelper.callLater(2.0, self.delayedAction, "Hello", world=True)
245
246
def delayedAction(self, message, world=False):
247
print(f"Delayed message: {message}")
248
if world:
249
print("World!")
250
251
# Set up application
252
app = AppKit.NSApplication.sharedApplication()
253
delegate = MyAppDelegate.alloc().init()
254
app.setDelegate_(delegate)
255
256
# Run event loop with exception handling
257
try:
258
AppHelper.runEventLoop()
259
except KeyboardInterrupt:
260
AppHelper.stopEventLoop()
261
```
262
263
### Cross-Thread Communication
264
265
```python
266
from PyObjCTools import AppHelper
267
import threading
268
import time
269
270
def background_work():
271
"""Simulate background work that needs to update UI."""
272
for i in range(10):
273
time.sleep(1)
274
# Update UI on main thread
275
AppHelper.callAfter(update_progress, i + 1)
276
277
# Final update
278
AppHelper.callAfter(work_completed)
279
280
def update_progress(value):
281
"""Called on main thread to update UI."""
282
print(f"Progress: {value}/10")
283
# Update progress bar, label, etc.
284
285
def work_completed():
286
"""Called on main thread when work is done."""
287
print("Background work completed!")
288
289
# Start background work
290
threading.Thread(target=background_work, daemon=True).start()
291
```
292
293
### Sheet Handling
294
295
```python
296
from PyObjCTools import AppHelper
297
import AppKit
298
299
class DocumentController(AppKit.NSObject):
300
@AppHelper.endSheetMethod
301
def saveSheetDidEnd_returnCode_contextInfo_(self, sheet, returnCode, contextInfo):
302
"""Handle save sheet completion."""
303
if returnCode == AppKit.NSModalResponseOK:
304
# User clicked Save
305
self.performSave()
306
else:
307
# User clicked Cancel
308
self.cancelSave()
309
310
def showSaveSheet(self):
311
"""Show save sheet with proper callback."""
312
save_panel = AppKit.NSSavePanel.savePanel()
313
save_panel.beginSheetModalForWindow_completionHandler_(
314
self.window,
315
self.saveSheetDidEnd_returnCode_contextInfo_
316
)
317
```
318
319
### Data Conversion
320
321
```python
322
from PyObjCTools import Conversion
323
import Foundation
324
import decimal
325
326
# Convert Python data to Objective-C property list
327
python_data = {
328
"name": "Test Application",
329
"version": 1.0,
330
"features": ["feature1", "feature2", "feature3"],
331
"enabled": True,
332
"config": {
333
"debug": False,
334
"timeout": 30
335
}
336
}
337
338
# Convert to Objective-C objects
339
objc_data = Conversion.propertyListFromPythonCollection(python_data)
340
341
# Serialize to XML format
342
xml_data = Conversion.serializePropertyList(objc_data, Conversion.FORMATS['xml'])
343
344
# Save to file
345
xml_data.writeToFile_atomically_("/tmp/config.plist", True)
346
347
# Read back and convert to Python
348
file_data = Foundation.NSData.dataWithContentsOfFile_("/tmp/config.plist")
349
deserialized, format_used = Conversion.deserializePropertyList(file_data)
350
python_data_back = Conversion.pythonCollectionFromPropertyList(deserialized)
351
352
print("Original:", python_data)
353
print("Restored:", python_data_back)
354
```
355
356
### Decimal Number Conversion
357
358
```python
359
from PyObjCTools import Conversion
360
import decimal
361
import Foundation
362
363
# High-precision decimal arithmetic
364
python_decimal = decimal.Decimal("123.456789012345678901234567890")
365
objc_decimal = Conversion.fromPythonDecimal(python_decimal)
366
367
# Use in calculations
368
multiplier = Foundation.NSDecimalNumber.decimalNumberWithString_("2.0")
369
result = objc_decimal.decimalNumberByMultiplyingBy_(multiplier)
370
371
# Convert back to Python
372
python_result = Conversion.toPythonDecimal(result)
373
print(f"Result: {python_result}") # Maintains full precision
374
```
375
376
### Enhanced Graphics Context
377
378
```python
379
from PyObjCTools import AppCategories # Auto-applies enhancements
380
import AppKit
381
382
def drawCustomShape(self):
383
"""Custom drawing method using enhanced graphics context."""
384
385
# Use context manager for automatic state management
386
with AppKit.NSGraphicsContext.savedGraphicsState():
387
# Set custom drawing state
388
AppKit.NSColor.blueColor().set()
389
path = AppKit.NSBezierPath.bezierPathWithOvalInRect_(self.bounds())
390
path.setLineWidth_(3.0)
391
path.stroke()
392
393
# Change state for different drawing
394
AppKit.NSColor.redColor().set()
395
inner_rect = self.bounds().insetBy(10.0, 10.0)
396
inner_path = AppKit.NSBezierPath.bezierPathWithRect_(inner_rect)
397
inner_path.fill()
398
399
# Graphics state automatically restored here
400
```
401
402
### Enhanced Animation Context
403
404
```python
405
from PyObjCTools import AppCategories # Auto-applies enhancements
406
import AppKit
407
408
def animateViewChanges(self):
409
"""Animate view changes using enhanced animation context."""
410
411
# Use context manager for animation grouping
412
with AppKit.NSAnimationContext.currentContext():
413
# Configure animation
414
AppKit.NSAnimationContext.currentContext().setDuration_(0.3)
415
AppKit.NSAnimationContext.currentContext().setTimingFunction_(
416
AppKit.CAMediaTimingFunction.functionWithName_(AppKit.kCAMediaTimingFunctionEaseInEaseOut)
417
)
418
419
# Animated changes
420
self.view.animator().setFrame_(new_frame)
421
self.view.animator().setAlphaValue_(0.5)
422
# Animation automatically committed on exit
423
```
424
425
### Transform Enhancements
426
427
```python
428
from PyObjCTools import FndCategories # Auto-applies enhancements
429
import Foundation
430
431
# Create and manipulate affine transform
432
transform = Foundation.NSAffineTransform.transform()
433
434
# Rotate around specific point using enhanced methods
435
center_point = Foundation.NSMakePoint(100, 100)
436
transform.rotateByDegrees_atPoint_(45.0, center_point)
437
438
# Or use radians
439
import math
440
transform.rotateByRadians_atPoint_(math.pi / 4, center_point)
441
442
# Apply transform to graphics context
443
transform.concat()
444
```
445
446
## Module Import Patterns
447
448
```python
449
# Individual tool imports
450
from PyObjCTools import AppHelper
451
from PyObjCTools import Conversion
452
453
# Category modules (auto-apply enhancements)
454
from PyObjCTools import AppCategories # Enhances AppKit classes
455
from PyObjCTools import FndCategories # Enhances Foundation classes
456
457
# Specific function imports
458
from PyObjCTools.AppHelper import runEventLoop, callAfter, callLater
459
from PyObjCTools.Conversion import pythonCollectionFromPropertyList, toPythonDecimal
460
```
461
462
## Error Handling
463
464
The PyObjCTools modules provide robust error handling:
465
466
- **AppHelper**: Exception handling in event loops prevents crashes
467
- **Conversion**: Validation of data types before conversion
468
- **Categories**: Safe method implementations with proper error propagation
469
470
All utilities are designed to work seamlessly with standard PyObjC exception handling and logging patterns.