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() 375