|
1 # A minimal text editor. |
|
2 # |
|
3 # To be done: |
|
4 # - Update viewrect after resize |
|
5 # - Handle horizontal scrollbar correctly |
|
6 # - Functionality: find, etc. |
|
7 |
|
8 from Carbon.Menu import DrawMenuBar |
|
9 from FrameWork import * |
|
10 from Carbon import Win |
|
11 from Carbon import Qd |
|
12 from Carbon import TE |
|
13 from Carbon import Scrap |
|
14 import os |
|
15 import macfs |
|
16 |
|
17 class TEWindow(ScrolledWindow): |
|
18 def open(self, path, name, data): |
|
19 self.path = path |
|
20 self.name = name |
|
21 r = windowbounds(400, 400) |
|
22 w = Win.NewWindow(r, name, 1, 0, -1, 1, 0) |
|
23 self.wid = w |
|
24 x0, y0, x1, y1 = self.wid.GetWindowPort().GetPortBounds() |
|
25 x0 = x0 + 4 |
|
26 y0 = y0 + 4 |
|
27 x1 = x1 - 20 |
|
28 y1 = y1 - 20 |
|
29 vr = dr = x0, y0, x1, y1 |
|
30 ##vr = 4, 0, r[2]-r[0]-15, r[3]-r[1]-15 |
|
31 ##dr = (0, 0, vr[2], 0) |
|
32 Qd.SetPort(w) |
|
33 Qd.TextFont(4) |
|
34 Qd.TextSize(9) |
|
35 self.ted = TE.TENew(dr, vr) |
|
36 self.ted.TEAutoView(1) |
|
37 self.ted.TESetText(data) |
|
38 w.DrawGrowIcon() |
|
39 self.scrollbars() |
|
40 self.changed = 0 |
|
41 self.do_postopen() |
|
42 self.do_activate(1, None) |
|
43 |
|
44 def do_idle(self): |
|
45 self.ted.TEIdle() |
|
46 |
|
47 def getscrollbarvalues(self): |
|
48 dr = self.ted.destRect |
|
49 vr = self.ted.viewRect |
|
50 height = self.ted.nLines * self.ted.lineHeight |
|
51 vx = self.scalebarvalue(dr[0], dr[2]-dr[0], vr[0], vr[2]) |
|
52 vy = self.scalebarvalue(dr[1], dr[1]+height, vr[1], vr[3]) |
|
53 print dr, vr, height, vx, vy |
|
54 return None, vy |
|
55 |
|
56 def scrollbar_callback(self, which, what, value): |
|
57 if which == 'y': |
|
58 if what == 'set': |
|
59 height = self.ted.nLines * self.ted.lineHeight |
|
60 cur = self.getscrollbarvalues()[1] |
|
61 delta = (cur-value)*height/32767 |
|
62 if what == '-': |
|
63 delta = self.ted.lineHeight |
|
64 elif what == '--': |
|
65 delta = (self.ted.viewRect[3]-self.ted.lineHeight) |
|
66 if delta <= 0: |
|
67 delta = self.ted.lineHeight |
|
68 elif what == '+': |
|
69 delta = -self.ted.lineHeight |
|
70 elif what == '++': |
|
71 delta = -(self.ted.viewRect[3]-self.ted.lineHeight) |
|
72 if delta >= 0: |
|
73 delta = -self.ted.lineHeight |
|
74 self.ted.TEPinScroll(0, delta) |
|
75 print 'SCROLL Y', delta |
|
76 else: |
|
77 pass # No horizontal scrolling |
|
78 |
|
79 def do_activate(self, onoff, evt): |
|
80 print "ACTIVATE", onoff |
|
81 ScrolledWindow.do_activate(self, onoff, evt) |
|
82 if onoff: |
|
83 self.ted.TEActivate() |
|
84 self.parent.active = self |
|
85 self.parent.updatemenubar() |
|
86 else: |
|
87 self.ted.TEDeactivate() |
|
88 |
|
89 def do_update(self, wid, event): |
|
90 Qd.EraseRect(wid.GetWindowPort().GetPortBounds()) |
|
91 self.ted.TEUpdate(wid.GetWindowPort().GetPortBounds()) |
|
92 self.updatescrollbars() |
|
93 |
|
94 def do_contentclick(self, local, modifiers, evt): |
|
95 shifted = (modifiers & 0x200) |
|
96 self.ted.TEClick(local, shifted) |
|
97 self.updatescrollbars() |
|
98 self.parent.updatemenubar() |
|
99 |
|
100 def do_char(self, ch, event): |
|
101 self.ted.TESelView() |
|
102 self.ted.TEKey(ord(ch)) |
|
103 self.changed = 1 |
|
104 self.updatescrollbars() |
|
105 self.parent.updatemenubar() |
|
106 |
|
107 def close(self): |
|
108 if self.changed: |
|
109 save = EasyDialogs.AskYesNoCancel('Save window "%s" before closing?'%self.name, 1) |
|
110 if save > 0: |
|
111 self.menu_save() |
|
112 elif save < 0: |
|
113 return |
|
114 if self.parent.active == self: |
|
115 self.parent.active = None |
|
116 self.parent.updatemenubar() |
|
117 del self.ted |
|
118 self.do_postclose() |
|
119 |
|
120 def menu_save(self): |
|
121 if not self.path: |
|
122 self.menu_save_as() |
|
123 return # Will call us recursively |
|
124 print 'Saving to ', self.path |
|
125 dhandle = self.ted.TEGetText() |
|
126 data = dhandle.data |
|
127 fp = open(self.path, 'wb') # NOTE: wb, because data has CR for end-of-line |
|
128 fp.write(data) |
|
129 if data[-1] <> '\r': fp.write('\r') |
|
130 fp.close() |
|
131 self.changed = 0 |
|
132 |
|
133 def menu_save_as(self): |
|
134 path = EasyDialogs.AskFileForSave(message='Save as:') |
|
135 if not path: return |
|
136 self.path = path |
|
137 self.name = os.path.split(self.path)[-1] |
|
138 self.wid.SetWTitle(self.name) |
|
139 self.menu_save() |
|
140 |
|
141 def menu_cut(self): |
|
142 self.ted.TESelView() |
|
143 self.ted.TECut() |
|
144 if hasattr(Scrap, 'ZeroScrap'): |
|
145 Scrap.ZeroScrap() |
|
146 else: |
|
147 Scrap.ClearCurrentScrap() |
|
148 TE.TEToScrap() |
|
149 self.updatescrollbars() |
|
150 self.parent.updatemenubar() |
|
151 self.changed = 1 |
|
152 |
|
153 def menu_copy(self): |
|
154 self.ted.TECopy() |
|
155 if hasattr(Scrap, 'ZeroScrap'): |
|
156 Scrap.ZeroScrap() |
|
157 else: |
|
158 Scrap.ClearCurrentScrap() |
|
159 TE.TEToScrap() |
|
160 self.updatescrollbars() |
|
161 self.parent.updatemenubar() |
|
162 |
|
163 def menu_paste(self): |
|
164 TE.TEFromScrap() |
|
165 self.ted.TESelView() |
|
166 self.ted.TEPaste() |
|
167 self.updatescrollbars() |
|
168 self.parent.updatemenubar() |
|
169 self.changed = 1 |
|
170 |
|
171 def menu_clear(self): |
|
172 self.ted.TESelView() |
|
173 self.ted.TEDelete() |
|
174 self.updatescrollbars() |
|
175 self.parent.updatemenubar() |
|
176 self.changed = 1 |
|
177 |
|
178 def have_selection(self): |
|
179 return (self.ted.selStart < self.ted.selEnd) |
|
180 |
|
181 class Ped(Application): |
|
182 def __init__(self): |
|
183 Application.__init__(self) |
|
184 self.num = 0 |
|
185 self.active = None |
|
186 self.updatemenubar() |
|
187 |
|
188 def makeusermenus(self): |
|
189 self.filemenu = m = Menu(self.menubar, "File") |
|
190 self.newitem = MenuItem(m, "New window", "N", self.open) |
|
191 self.openitem = MenuItem(m, "Open...", "O", self.openfile) |
|
192 self.closeitem = MenuItem(m, "Close", "W", self.closewin) |
|
193 m.addseparator() |
|
194 self.saveitem = MenuItem(m, "Save", "S", self.save) |
|
195 self.saveasitem = MenuItem(m, "Save as...", "", self.saveas) |
|
196 m.addseparator() |
|
197 self.quititem = MenuItem(m, "Quit", "Q", self.quit) |
|
198 |
|
199 self.editmenu = m = Menu(self.menubar, "Edit") |
|
200 self.undoitem = MenuItem(m, "Undo", "Z", self.undo) |
|
201 self.cutitem = MenuItem(m, "Cut", "X", self.cut) |
|
202 self.copyitem = MenuItem(m, "Copy", "C", self.copy) |
|
203 self.pasteitem = MenuItem(m, "Paste", "V", self.paste) |
|
204 self.clearitem = MenuItem(m, "Clear", "", self.clear) |
|
205 |
|
206 # Not yet implemented: |
|
207 self.undoitem.enable(0) |
|
208 |
|
209 # Groups of items enabled together: |
|
210 self.windowgroup = [self.closeitem, self.saveitem, self.saveasitem, self.editmenu] |
|
211 self.focusgroup = [self.cutitem, self.copyitem, self.clearitem] |
|
212 self.windowgroup_on = -1 |
|
213 self.focusgroup_on = -1 |
|
214 self.pastegroup_on = -1 |
|
215 |
|
216 def updatemenubar(self): |
|
217 changed = 0 |
|
218 on = (self.active <> None) |
|
219 if on <> self.windowgroup_on: |
|
220 for m in self.windowgroup: |
|
221 m.enable(on) |
|
222 self.windowgroup_on = on |
|
223 changed = 1 |
|
224 if on: |
|
225 # only if we have an edit menu |
|
226 on = self.active.have_selection() |
|
227 if on <> self.focusgroup_on: |
|
228 for m in self.focusgroup: |
|
229 m.enable(on) |
|
230 self.focusgroup_on = on |
|
231 changed = 1 |
|
232 if hasattr(Scrap, 'InfoScrap'): |
|
233 on = (Scrap.InfoScrap()[0] <> 0) |
|
234 else: |
|
235 flavors = Scrap.GetCurrentScrap().GetScrapFlavorInfoList() |
|
236 for tp, info in flavors: |
|
237 if tp == 'TEXT': |
|
238 on = 1 |
|
239 break |
|
240 else: |
|
241 on = 0 |
|
242 if on <> self.pastegroup_on: |
|
243 self.pasteitem.enable(on) |
|
244 self.pastegroup_on = on |
|
245 changed = 1 |
|
246 if changed: |
|
247 DrawMenuBar() |
|
248 |
|
249 # |
|
250 # Apple menu |
|
251 # |
|
252 |
|
253 def do_about(self, id, item, window, event): |
|
254 EasyDialogs.Message("A simple single-font text editor") |
|
255 |
|
256 # |
|
257 # File menu |
|
258 # |
|
259 |
|
260 def open(self, *args): |
|
261 self._open(0) |
|
262 |
|
263 def openfile(self, *args): |
|
264 self._open(1) |
|
265 |
|
266 def _open(self, askfile): |
|
267 if askfile: |
|
268 path = EasyDialogs.AskFileForOpen(typeList=('TEXT',)) |
|
269 if not path: |
|
270 return |
|
271 name = os.path.split(path)[-1] |
|
272 try: |
|
273 fp = open(path, 'rb') # NOTE binary, we need cr as end-of-line |
|
274 data = fp.read() |
|
275 fp.close() |
|
276 except IOError, arg: |
|
277 EasyDialogs.Message("IOERROR: %r" % (arg,)) |
|
278 return |
|
279 else: |
|
280 path = None |
|
281 name = "Untitled %d"%self.num |
|
282 data = '' |
|
283 w = TEWindow(self) |
|
284 w.open(path, name, data) |
|
285 self.num = self.num + 1 |
|
286 |
|
287 def closewin(self, *args): |
|
288 if self.active: |
|
289 self.active.close() |
|
290 else: |
|
291 EasyDialogs.Message("No active window?") |
|
292 |
|
293 def save(self, *args): |
|
294 if self.active: |
|
295 self.active.menu_save() |
|
296 else: |
|
297 EasyDialogs.Message("No active window?") |
|
298 |
|
299 def saveas(self, *args): |
|
300 if self.active: |
|
301 self.active.menu_save_as() |
|
302 else: |
|
303 EasyDialogs.Message("No active window?") |
|
304 |
|
305 |
|
306 def quit(self, *args): |
|
307 for w in self._windows.values(): |
|
308 w.close() |
|
309 if self._windows: |
|
310 return |
|
311 self._quit() |
|
312 |
|
313 # |
|
314 # Edit menu |
|
315 # |
|
316 |
|
317 def undo(self, *args): |
|
318 pass |
|
319 |
|
320 def cut(self, *args): |
|
321 if self.active: |
|
322 self.active.menu_cut() |
|
323 else: |
|
324 EasyDialogs.Message("No active window?") |
|
325 |
|
326 def copy(self, *args): |
|
327 if self.active: |
|
328 self.active.menu_copy() |
|
329 else: |
|
330 EasyDialogs.Message("No active window?") |
|
331 |
|
332 def paste(self, *args): |
|
333 if self.active: |
|
334 self.active.menu_paste() |
|
335 else: |
|
336 EasyDialogs.Message("No active window?") |
|
337 |
|
338 def clear(self, *args): |
|
339 if self.active: |
|
340 self.active.menu_clear() |
|
341 else: |
|
342 EasyDialogs.Message("No active window?") |
|
343 |
|
344 # |
|
345 # Other stuff |
|
346 # |
|
347 |
|
348 def idle(self, *args): |
|
349 if self.active: |
|
350 self.active.do_idle() |
|
351 else: |
|
352 Qd.SetCursor(Qd.GetQDGlobalsArrow()) |
|
353 |
|
354 def main(): |
|
355 App = Ped() |
|
356 App.mainloop() |
|
357 |
|
358 if __name__ == '__main__': |
|
359 main() |