Mac/Demo/mlte/mlted.py
# A minimal text editor using MLTE. Based on wed.py.
#
# To be done:
# - Functionality: find, etc.

from Menu import DrawMenuBar
from FrameWork import *
from Carbon import Win
from Carbon import Ctl
from Carbon import Qd
from Carbon import Res
from Carbon import Scrap
import os
from Carbon import MacTextEditor
from Carbon import Mlte

UNDOLABELS = [ # Indexed by MLTECanUndo() value
        "Typing", "Cut", "Paste", "Clear", "Font Change", "Color Change", "Size Change",
        "Style Change", "Align Left", "Align Center", "Align Right", "Drop", "Move"]

class MlteWindow(Window):
    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
        flags = MacTextEditor.kTXNDrawGrowIconMask|MacTextEditor.kTXNWantHScrollBarMask| \
                        MacTextEditor.kTXNWantVScrollBarMask
        self.ted, self.frameid = Mlte.TXNNewObject(None, w, None, flags, MacTextEditor.kTXNTextEditStyleFrameType,
                        MacTextEditor.kTXNTextFile, MacTextEditor.kTXNMacOSEncoding)
        self.ted.TXNSetData(MacTextEditor.kTXNTextData, data, 0, 0x7fffffff)
        self.changed = 0
        self.do_postopen()
        self.do_activate(1, None)

    def do_idle(self, event):
        self.ted.TXNIdle()
        self.ted.TXNAdjustCursor(None)



    def do_activate(self, onoff, evt):
        if onoff:
##                      self.ted.TXNActivate(self.frameid, 0)
            self.ted.TXNFocus(1)
            self.parent.active = self
        else:
            self.ted.TXNFocus(0)
            self.parent.active = None
        self.parent.updatemenubar()

    def do_update(self, wid, event):
        self.ted.TXNDraw(None)

    def do_postresize(self, width, height, window):
        self.ted.TXNResizeFrame(width, height, self.frameid)

    def do_contentclick(self, local, modifiers, evt):
        self.ted.TXNClick(evt)
        self.parent.updatemenubar()

    def do_char(self, ch, event):
        self.ted.TXNKeyDown(event)
        self.parent.updatemenubar()

    def close(self):
        if self.changed:
            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.ted.TXNDeleteObject()
        del self.ted
##              del self.tedtexthandle
        self.do_postclose()

    def menu_save(self):
        if not self.path:
            self.menu_save_as()
            return # Will call us recursively
        dhandle = self.ted.TXNGetData(0, 0x7fffffff)
        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()
        self.changed = 0

    def menu_save_as(self):
        path = EasyDialogs.AskFileForSave(message='Save as:')
        if not path: return
        self.path = path
        self.name = os.path.split(self.path)[-1]
        self.wid.SetWTitle(self.name)
        self.menu_save()

    def menu_cut(self):
##              self.ted.WESelView()
        self.ted.TXNCut()
###             Mlte.ConvertToPublicScrap()
##              Scrap.ZeroScrap()
##              self.ted.WECut()
##              self.updatescrollbars()
        self.parent.updatemenubar()
        self.changed = 1

    def menu_copy(self):
##              Scrap.ZeroScrap()
        self.ted.TXNCopy()
###             Mlte.ConvertToPublicScrap()
##              self.updatescrollbars()
        self.parent.updatemenubar()

    def menu_paste(self):
###             Mlte.ConvertFromPublicScrap()
        self.ted.TXNPaste()
##              self.updatescrollbars()
        self.parent.updatemenubar()
        self.changed = 1

    def menu_clear(self):
##              self.ted.WESelView()
        self.ted.TXNClear()
##              self.updatescrollbars()
        self.parent.updatemenubar()
        self.changed = 1

    def menu_undo(self):
        self.ted.TXNUndo()
##              self.updatescrollbars()
        self.parent.updatemenubar()

    def menu_redo(self):
        self.ted.TXNRedo()
##              self.updatescrollbars()
        self.parent.updatemenubar()

    def have_selection(self):
        start, stop = self.ted.TXNGetSelection()
        return start < stop

    def can_paste(self):
        return Mlte.TXNIsScrapPastable()

    def can_undo(self):
        can, which = self.ted.TXNCanUndo()
        if not can:
            return None
        if which >= len(UNDOLABELS):
            # Unspecified undo
            return "Undo"
        which = UNDOLABELS[which]

        return "Undo "+which

    def can_redo(self):
        can, which = self.ted.TXNCanRedo()
        if not can:
            return None
        if which >= len(UNDOLABELS):
            # Unspecified undo
            return "Redo"
        which = UNDOLABELS[which]

        return "Redo "+which

class Mlted(Application):
    def __init__(self):
        Application.__init__(self)
        self.num = 0
        self.active = None
        self.updatemenubar()

    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.redoitem = MenuItem(m, "Redo", None, self.redo)
        m.addseparator()
        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)

        # Groups of items enabled together:
        self.windowgroup = [self.closeitem, self.saveitem, self.saveasitem, self.editmenu]
        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.redo_label = "never"

    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
            on = self.active.can_redo()
            if on <> self.redo_label:
                if on:
                    self.redoitem.enable(1)
                    self.redoitem.settext(on)
                    self.redo_label = on
                else:
                    self.redoitem.settext("Nothing to redo")
                    self.redoitem.enable(0)
                changed = 1
        if changed:
            DrawMenuBar()

    #
    # Apple menu
    #

    def do_about(self, id, item, window, event):
        EasyDialogs.Message("A simple single-font text editor based on MacTextEditor")

    #
    # File menu
    #

    def open(self, *args):
        self._open(0)

    def openfile(self, *args):
        self._open(1)

    def _open(self, askfile):
        if askfile:
            path = EasyDialogs.AskFileForOpen(typeList=('TEXT',))
            if not path:
                return
            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: %r" % (arg,))
                return
        else:
            path = None
            name = "Untitled %d"%self.num
            data = ''
        w = MlteWindow(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 redo(self, *args):
        if self.active:
            self.active.menu_redo()
        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.GetQDGlobalsArrow())

def main():
    Mlte.TXNInitTextension(0)
    try:
        App = Mlted()
        App.mainloop()
    finally:
        Mlte.TXNTerminateTextension()

if __name__ == '__main__':
    main()