Guido van Rossum | f06ee5f | 1996-11-27 19:52:01 +0000 | [diff] [blame] | 1 | #! /usr/bin/env python |
Guido van Rossum | 9cf8f33 | 1992-03-30 10:54:51 +0000 | [diff] [blame] | 2 | |
| 3 | # A miniature multi-window editor using STDWIN's text objects. |
| 4 | # |
| 5 | # Usage: miniedit [file] ... |
| 6 | # |
| 7 | # The user interface is similar to that of the miniedit demo application |
| 8 | # in C that comes with STDWIN. |
| 9 | # |
| 10 | # XXX need to comment the functions |
| 11 | # XXX Not yet implemented: |
| 12 | # disabling menu entries for inapplicable actions |
| 13 | # Find operations |
| 14 | |
| 15 | |
| 16 | import sys |
| 17 | import stdwin |
| 18 | from stdwinevents import * |
| 19 | |
| 20 | |
| 21 | # Constant: list of WE_COMMAND events that (may) change the text buffer |
| 22 | # so we can decide whether to set the 'changed' flag. |
| 23 | # Note that it is possible for such a command to fail (a backspace |
| 24 | # at the beginning of the buffer) but we'll set the changed flag anyway |
| 25 | # -- it's too complicated to check this condition right now. |
| 26 | # |
| 27 | changing = [WC_RETURN, WC_TAB, WC_BACKSPACE] |
| 28 | |
| 29 | |
| 30 | # The list of currently open windows; |
| 31 | # this is maintained so we can stop when there are no windows left |
| 32 | # |
| 33 | windows = [] |
| 34 | |
| 35 | |
| 36 | # A note on window data attributes (set by open_window): |
| 37 | # |
| 38 | # w.textobject the window's text object |
| 39 | # w.changed true when the window's text is changed |
| 40 | # w.filename filename connected to the window; '' if none |
| 41 | |
| 42 | |
| 43 | # Main program |
| 44 | # |
| 45 | def main(): |
| 46 | # |
| 47 | # Set a reasonable default window size. |
| 48 | # If we are using a fixed-width font this will open a 80x24 window; |
| 49 | # for variable-width fonts we approximate this based on an average |
| 50 | # |
| 51 | stdwin.setdefwinsize(40*stdwin.textwidth('in'), 24*stdwin.lineheight()) |
| 52 | # |
| 53 | # Create global menus (as local variables) |
| 54 | # |
| 55 | filemenu = make_file_menu(stdwin) |
| 56 | editmenu = make_edit_menu(stdwin) |
| 57 | findmenu = make_find_menu(stdwin) |
| 58 | # |
| 59 | # Get the list of files from the command line (maybe none) |
| 60 | # |
| 61 | files = sys.argv[1:] |
| 62 | # |
| 63 | # Open any files -- errors will be reported but do won't stop us |
| 64 | # |
| 65 | for filename in files: |
| 66 | open_file(filename) |
| 67 | # |
| 68 | # If there were no files, or none of them could be opened, |
| 69 | # put up a dialog asking for a filename |
| 70 | # |
| 71 | if not windows: |
| 72 | try: |
| 73 | open_dialog(None) |
| 74 | except KeyboardInterrupt: |
| 75 | pass # User cancelled |
| 76 | # |
| 77 | # If the dialog was cancelled, create an empty new window |
| 78 | # |
| 79 | if not windows: |
| 80 | new_window(None) |
| 81 | # |
| 82 | # Main event loop -- stop when we have no open windows left |
| 83 | # |
| 84 | while windows: |
| 85 | # |
| 86 | # Get the next event -- ignore interrupts |
| 87 | # |
| 88 | try: |
| 89 | type, window, detail = event = stdwin.getevent() |
| 90 | except KeyboardInterrupt: |
| 91 | type, window, detail = event = WE_NONE, None, None |
| 92 | # |
| 93 | # Event decoding switch |
| 94 | # |
| 95 | if not window: |
| 96 | pass # Ignore such events |
| 97 | elif type == WE_MENU: |
| 98 | # |
| 99 | # Execute menu operation |
| 100 | # |
| 101 | menu, item = detail |
| 102 | try: |
| 103 | menu.actions[item](window) |
| 104 | except KeyboardInterrupt: |
| 105 | pass # User cancelled |
| 106 | elif type == WE_CLOSE: |
| 107 | # |
| 108 | # Close a window |
| 109 | # |
| 110 | try: |
| 111 | close_dialog(window) |
| 112 | except KeyboardInterrupt: |
| 113 | pass # User cancelled |
| 114 | elif type == WE_SIZE: |
| 115 | # |
| 116 | # A window was resized -- |
| 117 | # let the text object recompute the line breaks |
| 118 | # and change the document size accordingly, |
| 119 | # so scroll bars will work |
| 120 | # |
| 121 | fix_textsize(window) |
| 122 | elif window.textobject.event(event): |
| 123 | # |
| 124 | # The event was eaten by the text object -- |
| 125 | # set the changed flag if not already set |
| 126 | # |
| 127 | if type == WE_CHAR or \ |
| 128 | type == WE_COMMAND and detail in changing: |
| 129 | window.changed = 1 |
| 130 | fix_docsize(window) |
| 131 | # |
| 132 | # Delete all objects that may still reference the window |
| 133 | # in the event -- this is needed otherwise the window |
| 134 | # won't actually be closed and may receive further |
| 135 | # events, which will confuse the event decoder |
| 136 | # |
| 137 | del type, window, detail, event |
| 138 | |
| 139 | |
| 140 | def make_file_menu(object): |
| 141 | menu = object.menucreate('File') |
| 142 | menu.actions = [] |
| 143 | additem(menu, 'New', 'N', new_window) |
| 144 | additem(menu, 'Open..', 'O', open_dialog) |
| 145 | additem(menu, '', '', None) |
| 146 | additem(menu, 'Save', 'S', save_dialog) |
| 147 | additem(menu, 'Save As..', '', save_as_dialog) |
| 148 | additem(menu, 'Save a Copy..', '', save_copy_dialog) |
| 149 | additem(menu, 'Revert', 'R', revert_dialog) |
| 150 | additem(menu, 'Quit', 'Q', quit_dialog) |
| 151 | return menu |
| 152 | |
| 153 | |
| 154 | def make_edit_menu(object): |
| 155 | menu = object.menucreate('Edit') |
| 156 | menu.actions = [] |
| 157 | additem(menu, 'Cut', 'X', do_cut) |
| 158 | additem(menu, 'Copy', 'C', do_copy) |
| 159 | additem(menu, 'Paste', 'V', do_paste) |
| 160 | additem(menu, 'Clear', 'B', do_clear) |
| 161 | additem(menu, 'Select All', 'A', do_select_all) |
| 162 | return menu |
| 163 | |
| 164 | |
| 165 | def make_find_menu(object): |
| 166 | menu = object.menucreate('Find') |
| 167 | menu.actions = [] |
| 168 | # XXX |
| 169 | return menu |
| 170 | |
| 171 | |
| 172 | def additem(menu, text, shortcut, function): |
| 173 | if shortcut: |
| 174 | menu.additem(text, shortcut) |
| 175 | else: |
| 176 | menu.additem(text) |
| 177 | menu.actions.append(function) |
| 178 | |
| 179 | |
| 180 | def open_dialog(current_ignored): |
| 181 | filename = stdwin.askfile('Open file:', '', 0) |
| 182 | open_file(filename) |
| 183 | |
| 184 | |
| 185 | def open_file(filename): |
| 186 | try: |
| 187 | fp = open(filename, 'r') |
| 188 | except RuntimeError: |
| 189 | stdwin.message(filename + ': cannot open') |
| 190 | return # Error, forget it |
| 191 | try: |
| 192 | contents = fp.read() |
| 193 | except RuntimeError: |
| 194 | stdwin.message(filename + ': read error') |
| 195 | return # Error, forget it |
| 196 | del fp # Close the file |
| 197 | open_window(filename, filename, contents) |
| 198 | |
| 199 | |
| 200 | def new_window(current_ignored): |
| 201 | open_window('', 'Untitled', '') |
| 202 | |
| 203 | |
| 204 | def open_window(filename, title, contents): |
| 205 | try: |
| 206 | window = stdwin.open(title) |
| 207 | except RuntimeError: |
| 208 | stdwin.message('cannot open new window') |
| 209 | return # Error, forget it |
| 210 | window.textobject = window.textcreate((0, 0), window.getwinsize()) |
| 211 | window.textobject.settext(contents) |
| 212 | window.changed = 0 |
| 213 | window.filename = filename |
| 214 | fix_textsize(window) |
| 215 | windows.append(window) |
| 216 | |
| 217 | |
| 218 | def quit_dialog(window): |
| 219 | for window in windows[:]: |
| 220 | close_dialog(window) |
| 221 | |
| 222 | |
| 223 | def close_dialog(window): |
| 224 | if window.changed: |
| 225 | prompt = 'Save changes to ' + window.gettitle() + ' ?' |
| 226 | if stdwin.askync(prompt, 1): |
| 227 | save_dialog(window) |
| 228 | if window.changed: |
| 229 | return # Save failed (not) cancelled |
| 230 | windows.remove(window) |
| 231 | del window.textobject |
| 232 | |
| 233 | |
| 234 | def save_dialog(window): |
| 235 | if not window.filename: |
| 236 | save_as_dialog(window) |
| 237 | return |
| 238 | if save_file(window, window.filename): |
| 239 | window.changed = 0 |
| 240 | |
| 241 | |
| 242 | def save_as_dialog(window): |
| 243 | prompt = 'Save ' + window.gettitle() + ' as:' |
| 244 | filename = stdwin.askfile(prompt, window.filename, 1) |
| 245 | if save_file(window, filename): |
| 246 | window.filename = filename |
| 247 | window.settitle(filename) |
| 248 | window.changed = 0 |
| 249 | |
| 250 | |
| 251 | def save_copy_dialog(window): |
| 252 | prompt = 'Save a copy of ' + window.gettitle() + ' as:' |
| 253 | filename = stdwin.askfile(prompt, window.filename, 1) |
| 254 | void = save_file(window, filename) |
| 255 | |
| 256 | |
| 257 | def save_file(window, filename): |
| 258 | try: |
| 259 | fp = open(filename, 'w') |
| 260 | except RuntimeError: |
| 261 | stdwin.message(filename + ': cannot create') |
| 262 | return 0 |
| 263 | contents = window.textobject.gettext() |
| 264 | try: |
| 265 | fp.write(contents) |
| 266 | except RuntimeError: |
| 267 | stdwin.message(filename + ': write error') |
| 268 | return 0 |
| 269 | return 1 |
| 270 | |
| 271 | |
| 272 | def revert_dialog(window): |
| 273 | if not window.filename: |
| 274 | stdwin.message('This window has no file to revert from') |
| 275 | return |
| 276 | if window.changed: |
| 277 | prompt = 'Really read ' + window.filename + ' back from file?' |
| 278 | if not stdwin.askync(prompt, 1): |
| 279 | return |
| 280 | try: |
| 281 | fp = open(window.filename, 'r') |
| 282 | except RuntimeError: |
| 283 | stdwin.message(filename + ': cannot open') |
| 284 | return |
| 285 | contents = fp.read() |
| 286 | del fp # Close the file |
| 287 | window.textobject.settext(contents) |
| 288 | window.changed = 0 |
| 289 | fix_docsize(window) |
| 290 | |
| 291 | |
| 292 | def fix_textsize(window): |
| 293 | corner = window.getwinsize() |
| 294 | area = (0, 0), (corner) |
| 295 | window.textobject.move(area) |
| 296 | fix_docsize(window) |
| 297 | |
| 298 | |
| 299 | def fix_docsize(window): |
| 300 | area = window.textobject.getrect() |
| 301 | origin, corner = area |
| 302 | width, height = corner |
| 303 | window.setdocsize(0, height) |
| 304 | |
| 305 | |
| 306 | def do_cut(window): |
| 307 | selection = window.textobject.getfocustext() |
| 308 | if not selection: |
| 309 | stdwin.fleep() # Nothing to cut |
| 310 | elif not window.setselection(WS_PRIMARY, selection): |
| 311 | stdwin.fleep() # Window manager glitch... |
| 312 | else: |
| 313 | stdwin.rotatecutbuffers(1) |
| 314 | stdwin.setcutbuffer(0, selection) |
| 315 | window.textobject.replace('') |
| 316 | window.changed = 1 |
| 317 | fix_docsize(window) |
| 318 | |
| 319 | |
| 320 | def do_copy(window): |
| 321 | selection = window.textobject.getfocustext() |
| 322 | if not selection: |
| 323 | stdwin.fleep() # Nothing to cut |
| 324 | elif not window.setselection(WS_PRIMARY, selection): |
| 325 | stdwin.fleep() # Window manager glitch... |
| 326 | else: |
| 327 | stdwin.rotatecutbuffers(1) |
| 328 | stdwin.setcutbuffer(0, selection) |
| 329 | |
| 330 | |
| 331 | def do_paste(window): |
| 332 | selection = stdwin.getselection(WS_PRIMARY) |
| 333 | if not selection: |
| 334 | selection = stdwin.getcutbuffer(0) |
| 335 | if not selection: |
| 336 | stdwin.fleep() # Nothing to paste |
| 337 | else: |
| 338 | window.textobject.replace(selection) |
| 339 | window.changed = 1 |
| 340 | fix_docsize(window) |
| 341 | |
| 342 | def do_clear(window): |
| 343 | first, last = window.textobject.getfocus() |
| 344 | if first == last: |
| 345 | stdwin.fleep() # Nothing to clear |
| 346 | else: |
| 347 | window.textobject.replace('') |
| 348 | window.changed = 1 |
| 349 | fix_docsize(window) |
| 350 | |
| 351 | |
| 352 | def do_select_all(window): |
| 353 | window.textobject.setfocus(0, 0x7fffffff) # XXX Smaller on the Mac! |
| 354 | |
| 355 | |
| 356 | main() |