1# A minimal text editor using MLTE. Based on wed.py.
2#
3# To be done:
4# - Functionality: find, etc.
5
6from Menu import DrawMenuBar
7from FrameWork import *
8from Carbon import Win
9from Carbon import Ctl
10from Carbon import Qd
11from Carbon import Res
12from Carbon import Scrap
13import os
14from Carbon import MacTextEditor
15from Carbon import Mlte
16
17UNDOLABELS = [ # Indexed by MLTECanUndo() value
18        "Typing", "Cut", "Paste", "Clear", "Font Change", "Color Change", "Size Change",
19        "Style Change", "Align Left", "Align Center", "Align Right", "Drop", "Move"]
20
21class MlteWindow(Window):
22    def open(self, path, name, data):
23        self.path = path
24        self.name = name
25        r = windowbounds(400, 400)
26        w = Win.NewWindow(r, name, 1, 0, -1, 1, 0)
27        self.wid = w
28        flags = MacTextEditor.kTXNDrawGrowIconMask|MacTextEditor.kTXNWantHScrollBarMask| \
29                        MacTextEditor.kTXNWantVScrollBarMask
30        self.ted, self.frameid = Mlte.TXNNewObject(None, w, None, flags, MacTextEditor.kTXNTextEditStyleFrameType,
31                        MacTextEditor.kTXNTextFile, MacTextEditor.kTXNMacOSEncoding)
32        self.ted.TXNSetData(MacTextEditor.kTXNTextData, data, 0, 0x7fffffff)
33        self.changed = 0
34        self.do_postopen()
35        self.do_activate(1, None)
36
37    def do_idle(self, event):
38        self.ted.TXNIdle()
39        self.ted.TXNAdjustCursor(None)
40
41
42
43    def do_activate(self, onoff, evt):
44        if onoff:
45##                      self.ted.TXNActivate(self.frameid, 0)
46            self.ted.TXNFocus(1)
47            self.parent.active = self
48        else:
49            self.ted.TXNFocus(0)
50            self.parent.active = None
51        self.parent.updatemenubar()
52
53    def do_update(self, wid, event):
54        self.ted.TXNDraw(None)
55
56    def do_postresize(self, width, height, window):
57        self.ted.TXNResizeFrame(width, height, self.frameid)
58
59    def do_contentclick(self, local, modifiers, evt):
60        self.ted.TXNClick(evt)
61        self.parent.updatemenubar()
62
63    def do_char(self, ch, event):
64        self.ted.TXNKeyDown(event)
65        self.parent.updatemenubar()
66
67    def close(self):
68        if self.changed:
69            save = EasyDialogs.AskYesNoCancel('Save window "%s" before closing?'%self.name, 1)
70            if save > 0:
71                self.menu_save()
72            elif save < 0:
73                return
74        if self.parent.active == self:
75            self.parent.active = None
76        self.ted.TXNDeleteObject()
77        del self.ted
78##              del self.tedtexthandle
79        self.do_postclose()
80
81    def menu_save(self):
82        if not self.path:
83            self.menu_save_as()
84            return # Will call us recursively
85        dhandle = self.ted.TXNGetData(0, 0x7fffffff)
86        data = dhandle.data
87        fp = open(self.path, 'wb')  # NOTE: wb, because data has CR for end-of-line
88        fp.write(data)
89        if data[-1] <> '\r': fp.write('\r')
90        fp.close()
91        self.changed = 0
92
93    def menu_save_as(self):
94        path = EasyDialogs.AskFileForSave(message='Save as:')
95        if not path: return
96        self.path = path
97        self.name = os.path.split(self.path)[-1]
98        self.wid.SetWTitle(self.name)
99        self.menu_save()
100
101    def menu_cut(self):
102##              self.ted.WESelView()
103        self.ted.TXNCut()
104###             Mlte.ConvertToPublicScrap()
105##              Scrap.ZeroScrap()
106##              self.ted.WECut()
107##              self.updatescrollbars()
108        self.parent.updatemenubar()
109        self.changed = 1
110
111    def menu_copy(self):
112##              Scrap.ZeroScrap()
113        self.ted.TXNCopy()
114###             Mlte.ConvertToPublicScrap()
115##              self.updatescrollbars()
116        self.parent.updatemenubar()
117
118    def menu_paste(self):
119###             Mlte.ConvertFromPublicScrap()
120        self.ted.TXNPaste()
121##              self.updatescrollbars()
122        self.parent.updatemenubar()
123        self.changed = 1
124
125    def menu_clear(self):
126##              self.ted.WESelView()
127        self.ted.TXNClear()
128##              self.updatescrollbars()
129        self.parent.updatemenubar()
130        self.changed = 1
131
132    def menu_undo(self):
133        self.ted.TXNUndo()
134##              self.updatescrollbars()
135        self.parent.updatemenubar()
136
137    def menu_redo(self):
138        self.ted.TXNRedo()
139##              self.updatescrollbars()
140        self.parent.updatemenubar()
141
142    def have_selection(self):
143        start, stop = self.ted.TXNGetSelection()
144        return start < stop
145
146    def can_paste(self):
147        return Mlte.TXNIsScrapPastable()
148
149    def can_undo(self):
150        can, which = self.ted.TXNCanUndo()
151        if not can:
152            return None
153        if which >= len(UNDOLABELS):
154            # Unspecified undo
155            return "Undo"
156        which = UNDOLABELS[which]
157
158        return "Undo "+which
159
160    def can_redo(self):
161        can, which = self.ted.TXNCanRedo()
162        if not can:
163            return None
164        if which >= len(UNDOLABELS):
165            # Unspecified undo
166            return "Redo"
167        which = UNDOLABELS[which]
168
169        return "Redo "+which
170
171class Mlted(Application):
172    def __init__(self):
173        Application.__init__(self)
174        self.num = 0
175        self.active = None
176        self.updatemenubar()
177
178    def makeusermenus(self):
179        self.filemenu = m = Menu(self.menubar, "File")
180        self.newitem = MenuItem(m, "New window", "N", self.open)
181        self.openitem = MenuItem(m, "Open...", "O", self.openfile)
182        self.closeitem = MenuItem(m, "Close", "W", self.closewin)
183        m.addseparator()
184        self.saveitem = MenuItem(m, "Save", "S", self.save)
185        self.saveasitem = MenuItem(m, "Save as...", "", self.saveas)
186        m.addseparator()
187        self.quititem = MenuItem(m, "Quit", "Q", self.quit)
188
189        self.editmenu = m = Menu(self.menubar, "Edit")
190        self.undoitem = MenuItem(m, "Undo", "Z", self.undo)
191        self.redoitem = MenuItem(m, "Redo", None, self.redo)
192        m.addseparator()
193        self.cutitem = MenuItem(m, "Cut", "X", self.cut)
194        self.copyitem = MenuItem(m, "Copy", "C", self.copy)
195        self.pasteitem = MenuItem(m, "Paste", "V", self.paste)
196        self.clearitem = MenuItem(m, "Clear", "", self.clear)
197
198        # Groups of items enabled together:
199        self.windowgroup = [self.closeitem, self.saveitem, self.saveasitem, self.editmenu]
200        self.focusgroup = [self.cutitem, self.copyitem, self.clearitem]
201        self.windowgroup_on = -1
202        self.focusgroup_on = -1
203        self.pastegroup_on = -1
204        self.undo_label = "never"
205        self.redo_label = "never"
206
207    def updatemenubar(self):
208        changed = 0
209        on = (self.active <> None)
210        if on <> self.windowgroup_on:
211            for m in self.windowgroup:
212                m.enable(on)
213            self.windowgroup_on = on
214            changed = 1
215        if on:
216            # only if we have an edit menu
217            on = self.active.have_selection()
218            if on <> self.focusgroup_on:
219                for m in self.focusgroup:
220                    m.enable(on)
221                self.focusgroup_on = on
222                changed = 1
223            on = self.active.can_paste()
224            if on <> self.pastegroup_on:
225                self.pasteitem.enable(on)
226                self.pastegroup_on = on
227                changed = 1
228            on = self.active.can_undo()
229            if on <> self.undo_label:
230                if on:
231                    self.undoitem.enable(1)
232                    self.undoitem.settext(on)
233                    self.undo_label = on
234                else:
235                    self.undoitem.settext("Nothing to undo")
236                    self.undoitem.enable(0)
237                changed = 1
238            on = self.active.can_redo()
239            if on <> self.redo_label:
240                if on:
241                    self.redoitem.enable(1)
242                    self.redoitem.settext(on)
243                    self.redo_label = on
244                else:
245                    self.redoitem.settext("Nothing to redo")
246                    self.redoitem.enable(0)
247                changed = 1
248        if changed:
249            DrawMenuBar()
250
251    #
252    # Apple menu
253    #
254
255    def do_about(self, id, item, window, event):
256        EasyDialogs.Message("A simple single-font text editor based on MacTextEditor")
257
258    #
259    # File menu
260    #
261
262    def open(self, *args):
263        self._open(0)
264
265    def openfile(self, *args):
266        self._open(1)
267
268    def _open(self, askfile):
269        if askfile:
270            path = EasyDialogs.AskFileForOpen(typeList=('TEXT',))
271            if not path:
272                return
273            name = os.path.split(path)[-1]
274            try:
275                fp = open(path, 'rb') # NOTE binary, we need cr as end-of-line
276                data = fp.read()
277                fp.close()
278            except IOError, arg:
279                EasyDialogs.Message("IOERROR: %r" % (arg,))
280                return
281        else:
282            path = None
283            name = "Untitled %d"%self.num
284            data = ''
285        w = MlteWindow(self)
286        w.open(path, name, data)
287        self.num = self.num + 1
288
289    def closewin(self, *args):
290        if self.active:
291            self.active.close()
292        else:
293            EasyDialogs.Message("No active window?")
294
295    def save(self, *args):
296        if self.active:
297            self.active.menu_save()
298        else:
299            EasyDialogs.Message("No active window?")
300
301    def saveas(self, *args):
302        if self.active:
303            self.active.menu_save_as()
304        else:
305            EasyDialogs.Message("No active window?")
306
307
308    def quit(self, *args):
309        for w in self._windows.values():
310            w.close()
311        if self._windows:
312            return
313        self._quit()
314
315    #
316    # Edit menu
317    #
318
319    def undo(self, *args):
320        if self.active:
321            self.active.menu_undo()
322        else:
323            EasyDialogs.Message("No active window?")
324
325    def redo(self, *args):
326        if self.active:
327            self.active.menu_redo()
328        else:
329            EasyDialogs.Message("No active window?")
330
331    def cut(self, *args):
332        if self.active:
333            self.active.menu_cut()
334        else:
335            EasyDialogs.Message("No active window?")
336
337    def copy(self, *args):
338        if self.active:
339            self.active.menu_copy()
340        else:
341            EasyDialogs.Message("No active window?")
342
343    def paste(self, *args):
344        if self.active:
345            self.active.menu_paste()
346        else:
347            EasyDialogs.Message("No active window?")
348
349    def clear(self, *args):
350        if self.active:
351            self.active.menu_clear()
352        else:
353            EasyDialogs.Message("No active window?")
354
355    #
356    # Other stuff
357    #
358
359    def idle(self, event):
360        if self.active:
361            self.active.do_idle(event)
362        else:
363            Qd.SetCursor(Qd.GetQDGlobalsArrow())
364
365def main():
366    Mlte.TXNInitTextension(0)
367    try:
368        App = Mlted()
369        App.mainloop()
370    finally:
371        Mlte.TXNTerminateTextension()
372
373if __name__ == '__main__':
374    main()
375