fix to work on python <= 2.1
[python/dscho.git] / Mac / Demo / waste / swed.py
blobc85cb21c226fcba94391bfa8ce765a842003380a
1 # A minimal text editor.
3 # To be done:
4 # - Functionality: find, etc.
6 from Carbon.Menu import DrawMenuBar
7 from FrameWork import *
8 from Carbon import Win
9 from Carbon import Qd
10 from Carbon import Res
11 from Carbon import Fm
12 import waste
13 import WASTEconst
14 from Carbon import Scrap
15 import os
16 import macfs
18 UNDOLABELS = [ # Indexed by WEGetUndoInfo() value
19 None, "", "typing", "Cut", "Paste", "Clear", "Drag", "Style"]
21 # Style and size menu. Note that style order is important (tied to bit values)
22 STYLES = [
23 ("Bold", "B"), ("Italic", "I"), ("Underline", "U"), ("Outline", "O"),
24 ("Shadow", ""), ("Condensed", ""), ("Extended", "")
26 SIZES = [ 9, 10, 12, 14, 18, 24]
28 BIGREGION=Qd.NewRgn()
29 Qd.SetRectRgn(BIGREGION, -16000, -16000, 16000, 16000)
31 class WasteWindow(ScrolledWindow):
32 def open(self, path, name, data):
33 self.path = path
34 self.name = name
35 r = windowbounds(400, 400)
36 w = Win.NewWindow(r, name, 1, 0, -1, 1, 0)
37 self.wid = w
38 vr = 0, 0, r[2]-r[0]-15, r[3]-r[1]-15
39 dr = (0, 0, 10240, 0)
40 Qd.SetPort(w)
41 Qd.TextFont(4)
42 Qd.TextSize(9)
43 flags = WASTEconst.weDoAutoScroll | WASTEconst.weDoOutlineHilite | \
44 WASTEconst.weDoUndo
45 self.ted = waste.WENew(dr, vr, flags)
46 self.ted.WEInstallTabHooks()
47 style, soup = self.getstylesoup()
48 self.ted.WEInsert(data, style, soup)
49 self.ted.WESetSelection(0,0)
50 self.ted.WECalText()
51 self.ted.WEResetModCount()
52 w.DrawGrowIcon()
53 self.scrollbars()
54 self.do_postopen()
55 self.do_activate(1, None)
57 def getstylesoup(self):
58 if not self.path:
59 return None, None
60 oldrf = Res.CurResFile()
61 try:
62 rf = Res.FSpOpenResFile(self.path, 1)
63 except Res.Error:
64 return None, None
65 try:
66 hstyle = Res.Get1Resource('styl', 128)
67 hstyle.DetachResource()
68 except Res.Error:
69 hstyle = None
70 try:
71 hsoup = Res.Get1Resource('SOUP', 128)
72 hsoup.DetachResource()
73 except Res.Error:
74 hsoup = None
75 Res.CloseResFile(rf)
76 Res.UseResFile(oldrf)
77 return hstyle, hsoup
79 def do_idle(self, event):
80 (what, message, when, where, modifiers) = event
81 Qd.SetPort(self.wid)
82 self.ted.WEIdle()
83 if self.ted.WEAdjustCursor(where, BIGREGION):
84 return
85 Qd.SetCursor(Qd.GetQDGlobalsArrow())
87 def getscrollbarvalues(self):
88 dr = self.ted.WEGetDestRect()
89 vr = self.ted.WEGetViewRect()
90 vx = self.scalebarvalue(dr[0], dr[2], vr[0], vr[2])
91 vy = self.scalebarvalue(dr[1], dr[3], vr[1], vr[3])
92 return vx, vy
94 def scrollbar_callback(self, which, what, value):
95 if which == 'y':
96 if what == 'set':
97 height = self.ted.WEGetHeight(0, 0x3fffffff)
98 cur = self.getscrollbarvalues()[1]
99 delta = (cur-value)*height/32767
100 if what == '-':
101 topline_off,dummy = self.ted.WEGetOffset((1,1))
102 topline_num = self.ted.WEOffsetToLine(topline_off)
103 delta = self.ted.WEGetHeight(topline_num, topline_num+1)
104 elif what == '--':
105 delta = (self.ted.WEGetViewRect()[3]-10)
106 if delta <= 0:
107 delta = 10 # Random value
108 elif what == '+':
109 # XXXX Wrong: should be bottom line size
110 topline_off,dummy = self.ted.WEGetOffset((1,1))
111 topline_num = self.ted.WEOffsetToLine(topline_off)
112 delta = -self.ted.WEGetHeight(topline_num, topline_num+1)
113 elif what == '++':
114 delta = -(self.ted.WEGetViewRect()[3]-10)
115 if delta >= 0:
116 delta = -10
117 self.ted.WEScroll(0, delta)
118 else:
119 if what == 'set':
120 return # XXXX
121 vr = self.ted.WEGetViewRect()
122 winwidth = vr[2]-vr[0]
123 if what == '-':
124 delta = winwidth/10
125 elif what == '--':
126 delta = winwidth/2
127 elif what == '+':
128 delta = -winwidth/10
129 elif what == '++':
130 delta = -winwidth/2
131 self.ted.WEScroll(delta, 0)
132 # Pin the scroll
133 l, t, r, b = self.ted.WEGetDestRect()
134 vl, vt, vr, vb = self.ted.WEGetViewRect()
135 if t > 0 or l > 0:
136 dx = dy = 0
137 if t > 0: dy = -t
138 if l > 0: dx = -l
139 self.ted.WEScroll(dx, dy)
140 elif b < vb:
141 self.ted.WEScroll(0, b-vb)
144 def do_activate(self, onoff, evt):
145 Qd.SetPort(self.wid)
146 ScrolledWindow.do_activate(self, onoff, evt)
147 if onoff:
148 self.ted.WEActivate()
149 self.parent.active = self
150 self.parent.updatemenubar()
151 else:
152 self.ted.WEDeactivate()
154 def do_update(self, wid, event):
155 region = wid.GetWindowPort().visRgn
156 if Qd.EmptyRgn(region):
157 return
158 Qd.EraseRgn(region)
159 self.ted.WEUpdate(region)
160 self.updatescrollbars()
162 def do_postresize(self, width, height, window):
163 l, t, r, b = self.ted.WEGetViewRect()
164 vr = (l, t, l+width-15, t+height-15)
165 self.ted.WESetViewRect(vr)
166 self.wid.InvalWindowRect(vr)
167 ScrolledWindow.do_postresize(self, width, height, window)
169 def do_contentclick(self, local, modifiers, evt):
170 (what, message, when, where, modifiers) = evt
171 self.ted.WEClick(local, modifiers, when)
172 self.updatescrollbars()
173 self.parent.updatemenubar()
175 def do_char(self, ch, event):
176 self.ted.WESelView()
177 (what, message, when, where, modifiers) = event
178 self.ted.WEKey(ord(ch), modifiers)
179 self.updatescrollbars()
180 self.parent.updatemenubar()
182 def close(self):
183 if self.ted.WEGetModCount():
184 save = EasyDialogs.AskYesNoCancel('Save window "%s" before closing?'%self.name, 1)
185 if save > 0:
186 self.menu_save()
187 elif save < 0:
188 return
189 if self.parent.active == self:
190 self.parent.active = None
191 self.parent.updatemenubar()
192 del self.ted
193 self.do_postclose()
195 def menu_save(self):
196 if not self.path:
197 self.menu_save_as()
198 return # Will call us recursively
200 # First save data
202 dhandle = self.ted.WEGetText()
203 data = dhandle.data
204 fp = open(self.path, 'wb') # NOTE: wb, because data has CR for end-of-line
205 fp.write(data)
206 if data[-1] <> '\r': fp.write('\r')
207 fp.close()
209 # Now save style and soup
211 oldresfile = Res.CurResFile()
212 try:
213 rf = Res.FSpOpenResFile(self.path, 3)
214 except Res.Error:
215 Res.FSpCreateResFile(self.path, '????', 'TEXT', macfs.smAllScripts)
216 rf = Res.FSpOpenResFile(self.path, 3)
217 styles = Res.Resource('')
218 soup = Res.Resource('')
219 self.ted.WECopyRange(0, 0x3fffffff, None, styles, soup)
220 styles.AddResource('styl', 128, '')
221 soup.AddResource('SOUP', 128, '')
222 Res.CloseResFile(rf)
223 Res.UseResFile(oldresfile)
225 self.ted.WEResetModCount()
227 def menu_save_as(self):
228 path = EasyDialogs.AskFileForSave(message='Save as:')
229 if not path: return
230 self.path = path
231 self.name = os.path.split(self.path)[-1]
232 self.wid.SetWTitle(self.name)
233 self.menu_save()
235 def menu_cut(self):
236 self.ted.WESelView()
237 if hasattr(Scrap, 'ZeroScrap'):
238 Scrap.ZeroScrap()
239 else:
240 Scrap.ClearCurrentScrap()
241 self.ted.WECut()
242 self.updatescrollbars()
243 self.parent.updatemenubar()
245 def menu_copy(self):
246 if hasattr(Scrap, 'ZeroScrap'):
247 Scrap.ZeroScrap()
248 else:
249 Scrap.ClearCurrentScrap()
250 self.ted.WECopy()
251 self.updatescrollbars()
252 self.parent.updatemenubar()
254 def menu_paste(self):
255 self.ted.WESelView()
256 self.ted.WEPaste()
257 self.updatescrollbars()
258 self.parent.updatemenubar()
260 def menu_clear(self):
261 self.ted.WESelView()
262 self.ted.WEDelete()
263 self.updatescrollbars()
264 self.parent.updatemenubar()
266 def menu_undo(self):
267 self.ted.WEUndo()
268 self.updatescrollbars()
269 self.parent.updatemenubar()
271 def menu_setfont(self, font):
272 font = Fm.GetFNum(font)
273 self.mysetstyle(WASTEconst.weDoFont, (font, 0, 0, (0,0,0)))
274 self.parent.updatemenubar()
276 def menu_modface(self, face):
277 self.mysetstyle(WASTEconst.weDoFace|WASTEconst.weDoToggleFace,
278 (0, face, 0, (0,0,0)))
280 def menu_setface(self, face):
281 self.mysetstyle(WASTEconst.weDoFace|WASTEconst.weDoReplaceFace,
282 (0, face, 0, (0,0,0)))
284 def menu_setsize(self, size):
285 self.mysetstyle(WASTEconst.weDoSize, (0, 0, size, (0,0,0)))
287 def menu_incsize(self, size):
288 self.mysetstyle(WASTEconst.weDoAddSize, (0, 0, size, (0,0,0)))
290 def mysetstyle(self, which, how):
291 self.ted.WESelView()
292 self.ted.WESetStyle(which, how)
293 self.parent.updatemenubar()
295 def have_selection(self):
296 start, stop = self.ted.WEGetSelection()
297 return start < stop
299 def can_paste(self):
300 return self.ted.WECanPaste()
302 def can_undo(self):
303 which, redo = self.ted.WEGetUndoInfo()
304 which = UNDOLABELS[which]
305 if which == None: return None
306 if redo:
307 return "Redo "+which
308 else:
309 return "Undo "+which
311 def getruninfo(self):
312 all = (WASTEconst.weDoFont | WASTEconst.weDoFace | WASTEconst.weDoSize)
313 dummy, mode, (font, face, size, color) = self.ted.WEContinuousStyle(all)
314 if not (mode & WASTEconst.weDoFont):
315 font = None
316 else:
317 font = Fm.GetFontName(font)
318 if not (mode & WASTEconst.weDoFace): fact = None
319 if not (mode & WASTEconst.weDoSize): size = None
320 return font, face, size
322 class Wed(Application):
323 def __init__(self):
324 Application.__init__(self)
325 self.num = 0
326 self.active = None
327 self.updatemenubar()
328 waste.STDObjectHandlers()
330 def makeusermenus(self):
331 self.filemenu = m = Menu(self.menubar, "File")
332 self.newitem = MenuItem(m, "New window", "N", self.open)
333 self.openitem = MenuItem(m, "Open...", "O", self.openfile)
334 self.closeitem = MenuItem(m, "Close", "W", self.closewin)
335 m.addseparator()
336 self.saveitem = MenuItem(m, "Save", "S", self.save)
337 self.saveasitem = MenuItem(m, "Save as...", "", self.saveas)
338 m.addseparator()
339 self.quititem = MenuItem(m, "Quit", "Q", self.quit)
341 self.editmenu = m = Menu(self.menubar, "Edit")
342 self.undoitem = MenuItem(m, "Undo", "Z", self.undo)
343 self.cutitem = MenuItem(m, "Cut", "X", self.cut)
344 self.copyitem = MenuItem(m, "Copy", "C", self.copy)
345 self.pasteitem = MenuItem(m, "Paste", "V", self.paste)
346 self.clearitem = MenuItem(m, "Clear", "", self.clear)
348 self.makefontmenu()
350 # Groups of items enabled together:
351 self.windowgroup = [self.closeitem, self.saveitem, self.saveasitem,
352 self.editmenu, self.fontmenu, self.facemenu, self.sizemenu]
353 self.focusgroup = [self.cutitem, self.copyitem, self.clearitem]
354 self.windowgroup_on = -1
355 self.focusgroup_on = -1
356 self.pastegroup_on = -1
357 self.undo_label = "never"
358 self.ffs_values = ()
360 def makefontmenu(self):
361 self.fontmenu = Menu(self.menubar, "Font")
362 self.fontnames = getfontnames()
363 self.fontitems = []
364 for n in self.fontnames:
365 m = MenuItem(self.fontmenu, n, "", self.selfont)
366 self.fontitems.append(m)
367 self.facemenu = Menu(self.menubar, "Style")
368 self.faceitems = []
369 for n, shortcut in STYLES:
370 m = MenuItem(self.facemenu, n, shortcut, self.selface)
371 self.faceitems.append(m)
372 self.facemenu.addseparator()
373 self.faceitem_normal = MenuItem(self.facemenu, "Normal", "N",
374 self.selfacenormal)
375 self.sizemenu = Menu(self.menubar, "Size")
376 self.sizeitems = []
377 for n in SIZES:
378 m = MenuItem(self.sizemenu, `n`, "", self.selsize)
379 self.sizeitems.append(m)
380 self.sizemenu.addseparator()
381 self.sizeitem_bigger = MenuItem(self.sizemenu, "Bigger", "+",
382 self.selsizebigger)
383 self.sizeitem_smaller = MenuItem(self.sizemenu, "Smaller", "-",
384 self.selsizesmaller)
386 def selfont(self, id, item, *rest):
387 if self.active:
388 font = self.fontnames[item-1]
389 self.active.menu_setfont(font)
390 else:
391 EasyDialogs.Message("No active window?")
393 def selface(self, id, item, *rest):
394 if self.active:
395 face = (1<<(item-1))
396 self.active.menu_modface(face)
397 else:
398 EasyDialogs.Message("No active window?")
400 def selfacenormal(self, *rest):
401 if self.active:
402 self.active.menu_setface(0)
403 else:
404 EasyDialogs.Message("No active window?")
406 def selsize(self, id, item, *rest):
407 if self.active:
408 size = SIZES[item-1]
409 self.active.menu_setsize(size)
410 else:
411 EasyDialogs.Message("No active window?")
413 def selsizebigger(self, *rest):
414 if self.active:
415 self.active.menu_incsize(2)
416 else:
417 EasyDialogs.Message("No active window?")
419 def selsizesmaller(self, *rest):
420 if self.active:
421 self.active.menu_incsize(-2)
422 else:
423 EasyDialogs.Message("No active window?")
425 def updatemenubar(self):
426 changed = 0
427 on = (self.active <> None)
428 if on <> self.windowgroup_on:
429 for m in self.windowgroup:
430 m.enable(on)
431 self.windowgroup_on = on
432 changed = 1
433 if on:
434 # only if we have an edit menu
435 on = self.active.have_selection()
436 if on <> self.focusgroup_on:
437 for m in self.focusgroup:
438 m.enable(on)
439 self.focusgroup_on = on
440 changed = 1
441 on = self.active.can_paste()
442 if on <> self.pastegroup_on:
443 self.pasteitem.enable(on)
444 self.pastegroup_on = on
445 changed = 1
446 on = self.active.can_undo()
447 if on <> self.undo_label:
448 if on:
449 self.undoitem.enable(1)
450 self.undoitem.settext(on)
451 self.undo_label = on
452 else:
453 self.undoitem.settext("Nothing to undo")
454 self.undoitem.enable(0)
455 changed = 1
456 if self.updatefontmenus():
457 changed = 1
458 if changed:
459 DrawMenuBar()
461 def updatefontmenus(self):
462 info = self.active.getruninfo()
463 if info == self.ffs_values:
464 return 0
465 # Remove old checkmarks
466 if self.ffs_values == ():
467 self.ffs_values = (None, None, None)
468 font, face, size = self.ffs_values
469 if font <> None:
470 fnum = self.fontnames.index(font)
471 self.fontitems[fnum].check(0)
472 if face <> None:
473 for i in range(len(self.faceitems)):
474 if face & (1<<i):
475 self.faceitems[i].check(0)
476 if size <> None:
477 for i in range(len(self.sizeitems)):
478 if SIZES[i] == size:
479 self.sizeitems[i].check(0)
481 self.ffs_values = info
482 # Set new checkmarks
483 font, face, size = self.ffs_values
484 if font <> None:
485 fnum = self.fontnames.index(font)
486 self.fontitems[fnum].check(1)
487 if face <> None:
488 for i in range(len(self.faceitems)):
489 if face & (1<<i):
490 self.faceitems[i].check(1)
491 if size <> None:
492 for i in range(len(self.sizeitems)):
493 if SIZES[i] == size:
494 self.sizeitems[i].check(1)
495 # Set outline/normal for sizes
496 if font:
497 exists = getfontsizes(font, SIZES)
498 for i in range(len(self.sizeitems)):
499 if exists[i]:
500 self.sizeitems[i].setstyle(0)
501 else:
502 self.sizeitems[i].setstyle(8)
505 # Apple menu
508 def do_about(self, id, item, window, event):
509 EasyDialogs.Message("A simple single-font text editor based on WASTE")
512 # File menu
515 def open(self, *args):
516 self._open(0)
518 def openfile(self, *args):
519 self._open(1)
521 def _open(self, askfile):
522 if askfile:
523 path = EasyDialogs.AskFileForOpen(typeList=('TEXT',))
524 if not path:
525 return
526 name = os.path.split(path)[-1]
527 try:
528 fp = open(path, 'rb') # NOTE binary, we need cr as end-of-line
529 data = fp.read()
530 fp.close()
531 except IOError, arg:
532 EasyDialogs.Message("IOERROR: "+`arg`)
533 return
534 else:
535 path = None
536 name = "Untitled %d"%self.num
537 data = ''
538 w = WasteWindow(self)
539 w.open(path, name, data)
540 self.num = self.num + 1
542 def closewin(self, *args):
543 if self.active:
544 self.active.close()
545 else:
546 EasyDialogs.Message("No active window?")
548 def save(self, *args):
549 if self.active:
550 self.active.menu_save()
551 else:
552 EasyDialogs.Message("No active window?")
554 def saveas(self, *args):
555 if self.active:
556 self.active.menu_save_as()
557 else:
558 EasyDialogs.Message("No active window?")
561 def quit(self, *args):
562 for w in self._windows.values():
563 w.close()
564 if self._windows:
565 return
566 self._quit()
569 # Edit menu
572 def undo(self, *args):
573 if self.active:
574 self.active.menu_undo()
575 else:
576 EasyDialogs.Message("No active window?")
578 def cut(self, *args):
579 if self.active:
580 self.active.menu_cut()
581 else:
582 EasyDialogs.Message("No active window?")
584 def copy(self, *args):
585 if self.active:
586 self.active.menu_copy()
587 else:
588 EasyDialogs.Message("No active window?")
590 def paste(self, *args):
591 if self.active:
592 self.active.menu_paste()
593 else:
594 EasyDialogs.Message("No active window?")
596 def clear(self, *args):
597 if self.active:
598 self.active.menu_clear()
599 else:
600 EasyDialogs.Message("No active window?")
603 # Other stuff
606 def idle(self, event):
607 if self.active:
608 self.active.do_idle(event)
609 else:
610 Qd.SetCursor(Qd.GetQDGlobalsArrow())
612 def getfontnames():
613 names = []
614 for i in range(256):
615 n = Fm.GetFontName(i)
616 if n: names.append(n)
617 return names
619 def getfontsizes(name, sizes):
620 exist = []
621 num = Fm.GetFNum(name)
622 for sz in sizes:
623 if Fm.RealFont(num, sz):
624 exist.append(1)
625 else:
626 exist.append(0)
627 return exist
629 def main():
630 App = Wed()
631 App.mainloop()
633 if __name__ == '__main__':
634 main()