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