-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathCocoa.html
More file actions
181 lines (143 loc) · 7.3 KB
/
Copy pathCocoa.html
File metadata and controls
181 lines (143 loc) · 7.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html><head>
<title></title>
</head><body>
<div class="article">
<p>PlotDevice is built on <a href="http://pyobjc.sourceforge.net/">PyObjC</a>, a bridge between the Python and Objective-C programming languages. PyObjC's main use is writing <a href="http://developer.apple.com/cocoa/">Cocoa</a> applications for OS X in pure Python.</p>
<p>Cocoa is a set of "frameworks" that allow developers to create graphical applications with a typical Macintosh look-and-feel. Its two main libraries (<em>Foundation Kit</em> and <em>Application Kit</em>) are
bundled with the operating system. If you know a little PyObjC it’s easy to start playing around with all
the magic in OS X, right from your PlotDevice script.</p>
<hr/>
<p class="double-dagger"><em>This tutorial contains advanced material.</em></p>
<hr/>
<h2>AppKit manual</h2>
<p>The first thing you’ll notice about AppKit is that it has a <em>lot</em> of long, oddly-named classes all beginning with <em>NS</em>: NSBezierPath, NSRect, NSImage, and so
on (the <em>NS</em> stems from the fact that AppKit is a direct descendant of NeXTstep, the ’90s technology that revitalized Apple in the ’00s).</p>
<p>Crazily-verbose commands like </p>
<pre>layoutManager.drawGlyphsForGlyphRange_atPoint_(
glyphRange,
(x-dx, y-dy-self.font.defaultLineHeightForFont())
)</pre>
<p> are common. So the first thing to do is
get a manual. If you’ve installed Xcode, it provides a totally reasonable documentation viewer
accessible through the Help menu. On the web you can find Apple’s <a href="https://developer.apple.com/library/mac/documentation/cocoa/reference/applicationkit/objc_classic/_index.html">
AppKit</a> & <a href="https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/ObjC_classic/_index.html">
Foundation</a> references.</p>
<h2>AppKit rules of thumb</h2>
<p>Apple’s documentation lists all of the objects in their Objective-C form. To use them in Python, you have to do a bit of name-mangling to deal with the syntax differences. Each method's documentation begins with a "selector". For example, NSFontManager has a method with the selector:</p>
<pre>fontWithFamily:traits:weight:size:</pre>
<p>To figure out its PyObjC name, replace the colons with underscores, then pile all the arguments at the end (between parentheses). For instance, calling our NSFontManager would look like:</p>
<pre>NSFontManager.fontWithFamily_traits_weight_size_(fam, traits, wgt, sz)</pre>
<p>Another place where you need to work around the differences between Python and Obj-C is object instantiation. Cocoa objects use a two-phase procedure in which you first create an object with <em>object = Class.alloc()</em> and then initialise them with <em>object.init()</em> or a specialized initializer like <em>object.initWithARangeOfParamaters_(…)</em>.</p>
<p>Also note that many struct-like things in Foundation (like NSSize or NSRect) can have simple Python tuples substituted for them.</p>
<p>To start using AppKit in PlotDevice simply import the library:</p>
<pre>from Appkit import *
</pre>
<p>Now let's look at some of the things you can do with AppKit.</p>
<h2>Playing sounds</h2>
<p>The NSSound object in AppKit provides a very easy way to play AIFF and WAV sound files in
Mac applications. The class below is a PlotDevice wrapper for NSSound.</p>
<pre>from AppKit import NSSound
class Sound:
def __init__(self, file):
self._sound = NSSound.alloc()
self._sound.initWithContentsOfFile_byReference_(file, True)
def play(self): self._sound.play()
def stop(self): self._sound.stop()
def is_playing(self): return self._sound.isPlaying()
</pre>
<p>As you can see our <em>Sound</em> class takes a <em>file</em> parameter (that is the
location of your sound file) and returns an object with a number of methods:</p>
<ul>
<li><em>sound.play()</em>: start playing the sound
</li><li><em>sound.stop()</em>: stop playing
</li><li><em>sound.is_playing()</em>: returns True when playing
</li></ul>
<pre>woof = Sound("dog.aiff")
woof.play()
</pre>
<p>The following class defines a simple sound mixer/timeline. It has a number of channels that
play sounds at a defined time.</p>
<pre>from time import time
class Mixer:
def __init__(self, channels=4):
self.channels = [[] for i in range(channels)]
self.start = time()
self.playing = []
def queue(self, channel, time, file):
self.channels[channel].append( (time, Sound(file)) )
self.channels[channel].sort()
def play(self):
now = time() - self.start
for ch in self.channels:
if len(ch) > 0 and ch[0][0] < now:
self.playing.append(ch[0][1])
ch[0][1].play()
del ch[0]
def stop(self):
for sound in self.playing:
sound.stop()
self.playing = []
self.channels = [[] for ch in self.channels]
</pre>
<p>Queueing multiple sounds is now very easy:</p>
<pre>m = Mixer(2)
m.queue(0, 0.0, "woof.aiff")
m.queue(0, 0.4, "woof.aiff")
m.queue(0, 0.8, "woof.aiff")
m.queue(0, 1.2, "woof.aiff")
m.queue(1, 0.4, "meow.aiff")
m.queue(1, 1.2, "meow.aiff")
m.play()
</pre>
<h2>speech synthesis</h2>
<p>The example below wraps the NSSpeechSynthesizer in two PlotDevice commands. The
<em>voices()</em> command returns a list of all available voices. The <em>say()</em> command
makes PlotDevice speak out a sentence. The optional <em>voice</em> parameter sets the voice you
want to use.</p>
<pre>from AppKit import NSSpeechSynthesizer
def voices():
voices = NSSpeechSynthesizer.availableVoices()
voices = [x.split(".")[-1] for x in voices]
return voices
def say(txt, voice=None):
if voice in voices():
voice = "com.apple.speech.synthesis.voice."+voice
else:
voice = NSSpeechSynthesizer.defaultVoice()
speech = NSSpeechSynthesizer.alloc().initWithVoice_(voice)
speech.startSpeakingString_(txt)
</pre>
<p>Now say <em>hello</em> in a random voice:</p>
<pre>say("hello", voice=choice(voices()))
</pre>
<h2>Ransom Note Typography</h2>
<p>The command below wraps the NSFontManager object. It returns a list with the PostScript name
of each font installed on your system. It's similar to the built-in <a href="../ref/Misc.html#fonts()">fonts()</a> command, but it returns every individual weight and style rather than organizing things into "families". Thanks to Mark for this one. </p>
<pre>from AppKit import NSFontManager
def everyfont():
return NSFontManager.sharedFontManager().availableFonts()
</pre>
<p>Now you can do lots of fun typography:</p>
<pre>background(0.15, 0.1, 0.1)
font(14, leading=1.0)
x, y, h = 0, 0, 0
for f in everyfont()[:80]:
font(f)
width, height = measure(f)
# Random pink, blue or white color
fill(random(), random(0.5), 0.75)
if random() > 0.8: fill(1)
# Wrap text to the next line
if x + width > WIDTH:
x = 0
y += h
h = 0
text(f, x, y)
# Line height is equal to biggest font
h = max(h, height)
x += width
</pre>
<div class="media" style="margin:0;"><img height="550" src="../etc/tut/pyobjc-fonts.jpg" width="550"/></div>
</div>
</body></html>