# A minimal text editor. # # To be done: # - Functionality: find, etc. from Carbon.Menu import DrawMenuBar from FrameWork import * from Carbon import Win from Carbon import Qd from Carbon import Res from Carbon import Fm import waste import WASTEconst from Carbon import Scrap import os import macfs import MACFS UNDOLABELS = [ # Indexed by WEGetUndoInfo() value None, "", "typing", "Cut", "Paste", "Clear", "Drag", "Style"] # Style and size menu. Note that style order is important (tied to bit values) STYLES = [ ("Bold", "B"), ("Italic", "I"), ("Underline", "U"), ("Outline", "O"), ("Shadow", ""), ("Condensed", ""), ("Extended", "") ] SIZES = [ 9, 10, 12, 14, 18, 24] BIGREGION=Qd.NewRgn() Qd.SetRectRgn(BIGREGION, -16000, -16000, 16000, 16000) class WasteWindow(ScrolledWindow): def open(self, path, name, data): self.path = path self.name = name r = windowbounds(400, 400) w = Win.NewWindow(r, name, 1, 0, -1, 1, 0) self.wid = w vr = 0, 0, r[2]-r[0]-15, r[3]-r[1]-15 dr = (0, 0, 10240, 0) Qd.SetPort(w) Qd.TextFont(4) Qd.TextSize(9) flags = WASTEconst.weDoAutoScroll | WASTEconst.weDoOutlineHilite | \ WASTEconst.weDoUndo self.ted = waste.WENew(dr, vr, flags) self.ted.WEInstallTabHooks() style, soup = self.getstylesoup() self.ted.WEInsert(data, style, soup) self.ted.WESetSelection(0,0) self.ted.WECalText() self.ted.WEResetModCount() w.DrawGrowIcon() self.scrollbars() self.do_postopen() self.do_activate(1, None) def getstylesoup(self): if not self.path: return None, None oldrf = Res.CurResFile() try: rf = Res.FSpOpenResFile(self.path, 1) except Res.Error: return None, None try: hstyle = Res.Get1Resource('styl', 128) hstyle.DetachResource() except Res.Error: hstyle = None try: hsoup = Res.Get1Resource('SOUP', 128) hsoup.DetachResource() except Res.Error: hsoup = None Res.CloseResFile(rf) Res.UseResFile(oldrf) return hstyle, hsoup def do_idle(self, event): (what, message, when, where, modifiers) = event Qd.SetPort(self.wid) self.ted.WEIdle() if self.ted.WEAdjustCursor(where, BIGREGION): return Qd.SetCursor(Qd.qd.arrow) def getscrollbarvalues(self): dr = self.ted.WEGetDestRect() vr = self.ted.WEGetViewRect() vx = self.scalebarvalue(dr[0], dr[2], vr[0], vr[2]) vy = self.scalebarvalue(dr[1], dr[3], vr[1], vr[3]) return vx, vy def scrollbar_callback(self, which, what, value): if which == 'y': if what == 'set': height = self.ted.WEGetHeight(0, 0x3fffffff) cur = self.getscrollbarvalues()[1] delta = (cur-value)*height/32767 if what == '-': topline_off,dummy = self.ted.WEGetOffset((1,1)) topline_num = self.ted.WEOffsetToLine(topline_off) delta = self.ted.WEGetHeight(topline_num, topline_num+1) elif what == '--': delta = (self.ted.WEGetViewRect()[3]-10) if delta <= 0: delta = 10 # Random value elif what == '+': # XXXX Wrong: should be bottom line size topline_off,dummy = self.ted.WEGetOffset((1,1)) topline_num = self.ted.WEOffsetToLine(topline_off) delta = -self.ted.WEGetHeight(topline_num, topline_num+1) elif what == '++': delta = -(self.ted.WEGetViewRect()[3]-10) if delta >= 0: delta = -10 self.ted.WEScroll(0, delta) else: if what == 'set': return # XXXX vr = self.ted.WEGetViewRect() winwidth = vr[2]-vr[0] if what == '-': delta = winwidth/10 elif what == '--': delta = winwidth/2 elif what == '+': delta = -winwidth/10 elif what == '++': delta = -winwidth/2 self.ted.WEScroll(delta, 0) # Pin the scroll l, t, r, b = self.ted.WEGetDestRect() vl, vt, vr, vb = self.ted.WEGetViewRect() if t > 0 or l > 0: dx = dy = 0 if t > 0: dy = -t if l > 0: dx = -l self.ted.WEScroll(dx, dy) elif b < vb: self.ted.WEScroll(0, b-vb) def do_activate(self, onoff, evt): Qd.SetPort(self.wid) ScrolledWindow.do_activate(self, onoff, evt) if onoff: self.ted.WEActivate() self.parent.active = self self.parent.updatemenubar() else: self.ted.WEDeactivate() def do_update(self, wid, event): region = wid.GetWindowPort().visRgn if Qd.EmptyRgn(region): return Qd.EraseRgn(region) self.ted.WEUpdate(region) self.updatescrollbars() def do_postresize(self, width, height, window): l, t, r, b = self.ted.WEGetViewRect() vr = (l, t, l+width-15, t+height-15) self.ted.WESetViewRect(vr) self.wid.InvalWindowRect(vr) ScrolledWindow.do_postresize(self, width, height, window) def do_contentclick(self, local, modifiers, evt): (what, message, when, where, modifiers) = evt self.ted.WEClick(local, modifiers, when) self.updatescrollbars() self.parent.updatemenubar() def do_char(self, ch, event): self.ted.WESelView() (what, message, when, where, modifiers) = event self.ted.WEKey(ord(ch), modifiers) self.updatescrollbars() self.parent.updatemenubar() def close(self): if self.ted.WEGetModCount(): save = EasyDialogs.AskYesNoCancel('Save window "%s" before closing?'%self.name, 1) if save > 0: self.menu_save() elif save < 0: return if self.parent.active == self: self.parent.active = None self.parent.updatemenubar() del self.ted self.do_postclose() def menu_save(self): if not self.path: self.menu_save_as() return # Will call us recursively # # First save data # dhandle = self.ted.WEGetText() data = dhandle.data fp = open(self.path, 'wb') # NOTE: wb, because data has CR for end-of-line fp.write(data) if data[-1] <> '\r': fp.write('\r') fp.close() # # Now save style and soup # oldresfile = Res.CurResFile() try: rf = Res.FSpOpenResFile(self.path, 3) except Res.Error: Res.FSpCreateResFile(self.path, '????', 'TEXT', MACFS.smAllScripts) rf = Res.FSpOpenResFile(self.path, 3) styles = Res.Resource('') soup = Res.Resource('') self.ted.WECopyRange(0, 0x3fffffff, None, styles, soup) styles.AddResource('styl', 128, '') soup.AddResource('SOUP', 128, '') Res.CloseResFile(rf) Res.UseResFile(oldresfile) self.ted.WEResetModCount() def menu_save_as(self): fss, ok = macfs.StandardPutFile('Save as:') if not ok: return self.path = fss.as_pathname() self.name = os.path.split(self.path)[-1] self.wid.SetWTitle(self.name) self.menu_save() def menu_cut(self): self.ted.WESelView() if hasattr(Scrap, 'ZeroScrap'): Scrap.ZeroScrap() else: Scrap.ClearCurrentScrap() self.ted.WECut() self.updatescrollbars() self.parent.updatemenubar() def menu_copy(self): if hasattr(Scrap, 'ZeroScrap'): Scrap.ZeroScrap() else: Scrap.ClearCurrentScrap() self.ted.WECopy() self.updatescrollbars() self.parent.updatemenubar() def menu_paste(self): self.ted.WESelView() self.ted.WEPaste() self.updatescrollbars() self.parent.updatemenubar() def menu_clear(self): self.ted.WESelView() self.ted.WEDelete() self.updatescrollbars() self.parent.updatemenubar() def menu_undo(self): self.ted.WEUndo() self.updatescrollbars() self.parent.updatemenubar() def menu_setfont(self, font): font = Fm.GetFNum(font) self.mysetstyle(WASTEconst.weDoFont, (font, 0, 0, (0,0,0))) self.parent.updatemenubar() def menu_modface(self, face): self.mysetstyle(WASTEconst.weDoFace|WASTEconst.weDoToggleFace, (0, face, 0, (0,0,0))) def menu_setface(self, face): self.mysetstyle(WASTEconst.weDoFace|WASTEconst.weDoReplaceFace, (0, face, 0, (0,0,0))) def menu_setsize(self, size): self.mysetstyle(WASTEconst.weDoSize, (0, 0, size, (0,0,0))) def menu_incsize(self, size): self.mysetstyle(WASTEconst.weDoAddSize, (0, 0, size, (0,0,0))) def mysetstyle(self, which, how): self.ted.WESelView() self.ted.WESetStyle(which, how) self.parent.updatemenubar() def have_selection(self): start, stop = self.ted.WEGetSelection() return start < stop def can_paste(self): return self.ted.WECanPaste() def can_undo(self): which, redo = self.ted.WEGetUndoInfo() which = UNDOLABELS[which] if which == None: return None if redo: return "Redo "+which else: return "Undo "+which def getruninfo(self): all = (WASTEconst.weDoFont | WASTEconst.weDoFace | WASTEconst.weDoSize) dummy, mode, (font, face, size, color) = self.ted.WEContinuousStyle(all) if not (mode & WASTEconst.weDoFont): font = None else: font = Fm.GetFontName(font) if not (mode & WASTEconst.weDoFace): fact = None if not (mode & WASTEconst.weDoSize): size = None return font, face, size class Wed(Application): def __init__(self): Application.__init__(self) self.num = 0 self.active = None self.updatemenubar() waste.STDObjectHandlers() def makeusermenus(self): self.filemenu = m = Menu(self.menubar, "File") self.newitem = MenuItem(m, "New window", "N", self.open) self.openitem = MenuItem(m, "Open...", "O", self.openfile) self.closeitem = MenuItem(m, "Close", "W", self.closewin) m.addseparator() self.saveitem = MenuItem(m, "Save", "S", self.save) self.saveasitem = MenuItem(m, "Save as...", "", self.saveas) m.addseparator() self.quititem = MenuItem(m, "Quit", "Q", self.quit) self.editmenu = m = Menu(self.menubar, "Edit") self.undoitem = MenuItem(m, "Undo", "Z", self.undo) self.cutitem = MenuItem(m, "Cut", "X", self.cut) self.copyitem = MenuItem(m, "Copy", "C", self.copy) self.pasteitem = MenuItem(m, "Paste", "V", self.paste) self.clearitem = MenuItem(m, "Clear", "", self.clear) self.makefontmenu() # Groups of items enabled together: self.windowgroup = [self.closeitem, self.saveitem, self.saveasitem, self.editmenu, self.fontmenu, self.facemenu, self.sizemenu] self.focusgroup = [self.cutitem, self.copyitem, self.clearitem] self.windowgroup_on = -1 self.focusgroup_on = -1 self.pastegroup_on = -1 self.undo_label = "never" self.ffs_values = () def makefontmenu(self): self.fontmenu = Menu(self.menubar, "Font") self.fontnames = getfontnames() self.fontitems = [] for n in self.fontnames: m = MenuItem(self.fontmenu, n, "", self.selfont) self.fontitems.append(m) self.facemenu = Menu(self.menubar, "Style") self.faceitems = [] for n, shortcut in STYLES: m = MenuItem(self.facemenu, n, shortcut, self.selface) self.faceitems.append(m) self.facemenu.addseparator() self.faceitem_normal = MenuItem(self.facemenu, "Normal", "N", self.selfacenormal) self.sizemenu = Menu(self.menubar, "Size") self.sizeitems = [] for n in SIZES: m = MenuItem(self.sizemenu, `n`, "", self.selsize) self.sizeitems.append(m) self.sizemenu.addseparator() self.sizeitem_bigger = MenuItem(self.sizemenu, "Bigger", "+", self.selsizebigger) self.sizeitem_smaller = MenuItem(self.sizemenu, "Smaller", "-", self.selsizesmaller) def selfont(self, id, item, *rest): if self.active: font = self.fontnames[item-1] self.active.menu_setfont(font) else: EasyDialogs.Message("No active window?") def selface(self, id, item, *rest): if self.active: face = (1<<(item-1)) self.active.menu_modface(face) else: EasyDialogs.Message("No active window?") def selfacenormal(self, *rest): if self.active: self.active.menu_setface(0) else: EasyDialogs.Message("No active window?") def selsize(self, id, item, *rest): if self.active: size = SIZES[item-1] self.active.menu_setsize(size) else: EasyDialogs.Message("No active window?") def selsizebigger(self, *rest): if self.active: self.active.menu_incsize(2) else: EasyDialogs.Message("No active window?") def selsizesmaller(self, *rest): if self.active: self.active.menu_incsize(-2) else: EasyDialogs.Message("No active window?") def updatemenubar(self): changed = 0 on = (self.active <> None) if on <> self.windowgroup_on: for m in self.windowgroup: m.enable(on) self.windowgroup_on = on changed = 1 if on: # only if we have an edit menu on = self.active.have_selection() if on <> self.focusgroup_on: for m in self.focusgroup: m.enable(on) self.focusgroup_on = on changed = 1 on = self.active.can_paste() if on <> self.pastegroup_on: self.pasteitem.enable(on) self.pastegroup_on = on changed = 1 on = self.active.can_undo() if on <> self.undo_label: if on: self.undoitem.enable(1) self.undoitem.settext(on) self.undo_label = on else: self.undoitem.settext("Nothing to undo") self.undoitem.enable(0) changed = 1 if self.updatefontmenus(): changed = 1 if changed: DrawMenuBar() def updatefontmenus(self): info = self.active.getruninfo() if info == self.ffs_values: return 0 # Remove old checkmarks if self.ffs_values == (): self.ffs_values = (None, None, None) font, face, size = self.ffs_values if font <> None: fnum = self.fontnames.index(font) self.fontitems[fnum].check(0) if face <> None: for i in range(len(self.faceitems)): if face & (1< None: for i in range(len(self.sizeitems)): if SIZES[i] == size: self.sizeitems[i].check(0) self.ffs_values = info # Set new checkmarks font, face, size = self.ffs_values if font <> None: fnum = self.fontnames.index(font) self.fontitems[fnum].check(1) if face <> None: for i in range(len(self.faceitems)): if face & (1< None: for i in range(len(self.sizeitems)): if SIZES[i] == size: self.sizeitems[i].check(1) # Set outline/normal for sizes if font: exists = getfontsizes(font, SIZES) for i in range(len(self.sizeitems)): if exists[i]: self.sizeitems[i].setstyle(0) else: self.sizeitems[i].setstyle(8) # # Apple menu # def do_about(self, id, item, window, event): EasyDialogs.Message("A simple single-font text editor based on WASTE") # # File menu # def open(self, *args): self._open(0) def openfile(self, *args): self._open(1) def _open(self, askfile): if askfile: fss, ok = macfs.StandardGetFile('TEXT') if not ok: return path = fss.as_pathname() name = os.path.split(path)[-1] try: fp = open(path, 'rb') # NOTE binary, we need cr as end-of-line data = fp.read() fp.close() except IOError, arg: EasyDialogs.Message("IOERROR: "+`arg`) return else: path = None name = "Untitled %d"%self.num data = '' w = WasteWindow(self) w.open(path, name, data) self.num = self.num + 1 def closewin(self, *args): if self.active: self.active.close() else: EasyDialogs.Message("No active window?") def save(self, *args): if self.active: self.active.menu_save() else: EasyDialogs.Message("No active window?") def saveas(self, *args): if self.active: self.active.menu_save_as() else: EasyDialogs.Message("No active window?") def quit(self, *args): for w in self._windows.values(): w.close() if self._windows: return self._quit() # # Edit menu # def undo(self, *args): if self.active: self.active.menu_undo() else: EasyDialogs.Message("No active window?") def cut(self, *args): if self.active: self.active.menu_cut() else: EasyDialogs.Message("No active window?") def copy(self, *args): if self.active: self.active.menu_copy() else: EasyDialogs.Message("No active window?") def paste(self, *args): if self.active: self.active.menu_paste() else: EasyDialogs.Message("No active window?") def clear(self, *args): if self.active: self.active.menu_clear() else: EasyDialogs.Message("No active window?") # # Other stuff # def idle(self, event): if self.active: self.active.do_idle(event) else: Qd.SetCursor(Qd.qd.arrow) def getfontnames(): names = [] for i in range(256): n = Fm.GetFontName(i) if n: names.append(n) return names def getfontsizes(name, sizes): exist = [] num = Fm.GetFNum(name) for sz in sizes: if Fm.RealFont(num, sz): exist.append(1) else: exist.append(0) return exist def main(): App = Wed() App.mainloop() if __name__ == '__main__': main()