|
1 # A minimal text editor using MLTE. Based on wed.py. |
|
2 # |
|
3 # To be done: |
|
4 # - Functionality: find, etc. |
|
5 |
|
6 from Menu import DrawMenuBar |
|
7 from FrameWork import * |
|
8 from Carbon import Win |
|
9 from Carbon import Ctl |
|
10 from Carbon import Qd |
|
11 from Carbon import Res |
|
12 from Carbon import Scrap |
|
13 import os |
|
14 from Carbon import MacTextEditor |
|
15 from Carbon import Mlte |
|
16 |
|
17 UNDOLABELS = [ # 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 |
|
21 class 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 |
|
171 class 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 |
|
365 def main(): |
|
366 Mlte.TXNInitTextension(0) |
|
367 try: |
|
368 App = Mlted() |
|
369 App.mainloop() |
|
370 finally: |
|
371 Mlte.TXNTerminateTextension() |
|
372 |
|
373 if __name__ == '__main__': |
|
374 main() |