| #! /usr/bin/env python |
| |
| # A miniature multi-window editor using STDWIN's text objects. |
| # |
| # Usage: miniedit [file] ... |
| # |
| # The user interface is similar to that of the miniedit demo application |
| # in C that comes with STDWIN. |
| # |
| # XXX need to comment the functions |
| # XXX Not yet implemented: |
| # disabling menu entries for inapplicable actions |
| # Find operations |
| |
| |
| import sys |
| import stdwin |
| from stdwinevents import * |
| |
| |
| # Constant: list of WE_COMMAND events that (may) change the text buffer |
| # so we can decide whether to set the 'changed' flag. |
| # Note that it is possible for such a command to fail (a backspace |
| # at the beginning of the buffer) but we'll set the changed flag anyway |
| # -- it's too complicated to check this condition right now. |
| # |
| changing = [WC_RETURN, WC_TAB, WC_BACKSPACE] |
| |
| |
| # The list of currently open windows; |
| # this is maintained so we can stop when there are no windows left |
| # |
| windows = [] |
| |
| |
| # A note on window data attributes (set by open_window): |
| # |
| # w.textobject the window's text object |
| # w.changed true when the window's text is changed |
| # w.filename filename connected to the window; '' if none |
| |
| |
| # Main program |
| # |
| def main(): |
| # |
| # Set a reasonable default window size. |
| # If we are using a fixed-width font this will open a 80x24 window; |
| # for variable-width fonts we approximate this based on an average |
| # |
| stdwin.setdefwinsize(40*stdwin.textwidth('in'), 24*stdwin.lineheight()) |
| # |
| # Create global menus (as local variables) |
| # |
| filemenu = make_file_menu(stdwin) |
| editmenu = make_edit_menu(stdwin) |
| findmenu = make_find_menu(stdwin) |
| # |
| # Get the list of files from the command line (maybe none) |
| # |
| files = sys.argv[1:] |
| # |
| # Open any files -- errors will be reported but do won't stop us |
| # |
| for filename in files: |
| open_file(filename) |
| # |
| # If there were no files, or none of them could be opened, |
| # put up a dialog asking for a filename |
| # |
| if not windows: |
| try: |
| open_dialog(None) |
| except KeyboardInterrupt: |
| pass # User cancelled |
| # |
| # If the dialog was cancelled, create an empty new window |
| # |
| if not windows: |
| new_window(None) |
| # |
| # Main event loop -- stop when we have no open windows left |
| # |
| while windows: |
| # |
| # Get the next event -- ignore interrupts |
| # |
| try: |
| type, window, detail = event = stdwin.getevent() |
| except KeyboardInterrupt: |
| type, window, detail = event = WE_NONE, None, None |
| # |
| # Event decoding switch |
| # |
| if not window: |
| pass # Ignore such events |
| elif type == WE_MENU: |
| # |
| # Execute menu operation |
| # |
| menu, item = detail |
| try: |
| menu.actions[item](window) |
| except KeyboardInterrupt: |
| pass # User cancelled |
| elif type == WE_CLOSE: |
| # |
| # Close a window |
| # |
| try: |
| close_dialog(window) |
| except KeyboardInterrupt: |
| pass # User cancelled |
| elif type == WE_SIZE: |
| # |
| # A window was resized -- |
| # let the text object recompute the line breaks |
| # and change the document size accordingly, |
| # so scroll bars will work |
| # |
| fix_textsize(window) |
| elif window.textobject.event(event): |
| # |
| # The event was eaten by the text object -- |
| # set the changed flag if not already set |
| # |
| if type == WE_CHAR or \ |
| type == WE_COMMAND and detail in changing: |
| window.changed = 1 |
| fix_docsize(window) |
| # |
| # Delete all objects that may still reference the window |
| # in the event -- this is needed otherwise the window |
| # won't actually be closed and may receive further |
| # events, which will confuse the event decoder |
| # |
| del type, window, detail, event |
| |
| |
| def make_file_menu(object): |
| menu = object.menucreate('File') |
| menu.actions = [] |
| additem(menu, 'New', 'N', new_window) |
| additem(menu, 'Open..', 'O', open_dialog) |
| additem(menu, '', '', None) |
| additem(menu, 'Save', 'S', save_dialog) |
| additem(menu, 'Save As..', '', save_as_dialog) |
| additem(menu, 'Save a Copy..', '', save_copy_dialog) |
| additem(menu, 'Revert', 'R', revert_dialog) |
| additem(menu, 'Quit', 'Q', quit_dialog) |
| return menu |
| |
| |
| def make_edit_menu(object): |
| menu = object.menucreate('Edit') |
| menu.actions = [] |
| additem(menu, 'Cut', 'X', do_cut) |
| additem(menu, 'Copy', 'C', do_copy) |
| additem(menu, 'Paste', 'V', do_paste) |
| additem(menu, 'Clear', 'B', do_clear) |
| additem(menu, 'Select All', 'A', do_select_all) |
| return menu |
| |
| |
| def make_find_menu(object): |
| menu = object.menucreate('Find') |
| menu.actions = [] |
| # XXX |
| return menu |
| |
| |
| def additem(menu, text, shortcut, function): |
| if shortcut: |
| menu.additem(text, shortcut) |
| else: |
| menu.additem(text) |
| menu.actions.append(function) |
| |
| |
| def open_dialog(current_ignored): |
| filename = stdwin.askfile('Open file:', '', 0) |
| open_file(filename) |
| |
| |
| def open_file(filename): |
| try: |
| fp = open(filename, 'r') |
| except RuntimeError: |
| stdwin.message(filename + ': cannot open') |
| return # Error, forget it |
| try: |
| contents = fp.read() |
| except RuntimeError: |
| stdwin.message(filename + ': read error') |
| return # Error, forget it |
| del fp # Close the file |
| open_window(filename, filename, contents) |
| |
| |
| def new_window(current_ignored): |
| open_window('', 'Untitled', '') |
| |
| |
| def open_window(filename, title, contents): |
| try: |
| window = stdwin.open(title) |
| except RuntimeError: |
| stdwin.message('cannot open new window') |
| return # Error, forget it |
| window.textobject = window.textcreate((0, 0), window.getwinsize()) |
| window.textobject.settext(contents) |
| window.changed = 0 |
| window.filename = filename |
| fix_textsize(window) |
| windows.append(window) |
| |
| |
| def quit_dialog(window): |
| for window in windows[:]: |
| close_dialog(window) |
| |
| |
| def close_dialog(window): |
| if window.changed: |
| prompt = 'Save changes to ' + window.gettitle() + ' ?' |
| if stdwin.askync(prompt, 1): |
| save_dialog(window) |
| if window.changed: |
| return # Save failed (not) cancelled |
| windows.remove(window) |
| del window.textobject |
| |
| |
| def save_dialog(window): |
| if not window.filename: |
| save_as_dialog(window) |
| return |
| if save_file(window, window.filename): |
| window.changed = 0 |
| |
| |
| def save_as_dialog(window): |
| prompt = 'Save ' + window.gettitle() + ' as:' |
| filename = stdwin.askfile(prompt, window.filename, 1) |
| if save_file(window, filename): |
| window.filename = filename |
| window.settitle(filename) |
| window.changed = 0 |
| |
| |
| def save_copy_dialog(window): |
| prompt = 'Save a copy of ' + window.gettitle() + ' as:' |
| filename = stdwin.askfile(prompt, window.filename, 1) |
| void = save_file(window, filename) |
| |
| |
| def save_file(window, filename): |
| try: |
| fp = open(filename, 'w') |
| except RuntimeError: |
| stdwin.message(filename + ': cannot create') |
| return 0 |
| contents = window.textobject.gettext() |
| try: |
| fp.write(contents) |
| except RuntimeError: |
| stdwin.message(filename + ': write error') |
| return 0 |
| return 1 |
| |
| |
| def revert_dialog(window): |
| if not window.filename: |
| stdwin.message('This window has no file to revert from') |
| return |
| if window.changed: |
| prompt = 'Really read ' + window.filename + ' back from file?' |
| if not stdwin.askync(prompt, 1): |
| return |
| try: |
| fp = open(window.filename, 'r') |
| except RuntimeError: |
| stdwin.message(filename + ': cannot open') |
| return |
| contents = fp.read() |
| del fp # Close the file |
| window.textobject.settext(contents) |
| window.changed = 0 |
| fix_docsize(window) |
| |
| |
| def fix_textsize(window): |
| corner = window.getwinsize() |
| area = (0, 0), (corner) |
| window.textobject.move(area) |
| fix_docsize(window) |
| |
| |
| def fix_docsize(window): |
| area = window.textobject.getrect() |
| origin, corner = area |
| width, height = corner |
| window.setdocsize(0, height) |
| |
| |
| def do_cut(window): |
| selection = window.textobject.getfocustext() |
| if not selection: |
| stdwin.fleep() # Nothing to cut |
| elif not window.setselection(WS_PRIMARY, selection): |
| stdwin.fleep() # Window manager glitch... |
| else: |
| stdwin.rotatecutbuffers(1) |
| stdwin.setcutbuffer(0, selection) |
| window.textobject.replace('') |
| window.changed = 1 |
| fix_docsize(window) |
| |
| |
| def do_copy(window): |
| selection = window.textobject.getfocustext() |
| if not selection: |
| stdwin.fleep() # Nothing to cut |
| elif not window.setselection(WS_PRIMARY, selection): |
| stdwin.fleep() # Window manager glitch... |
| else: |
| stdwin.rotatecutbuffers(1) |
| stdwin.setcutbuffer(0, selection) |
| |
| |
| def do_paste(window): |
| selection = stdwin.getselection(WS_PRIMARY) |
| if not selection: |
| selection = stdwin.getcutbuffer(0) |
| if not selection: |
| stdwin.fleep() # Nothing to paste |
| else: |
| window.textobject.replace(selection) |
| window.changed = 1 |
| fix_docsize(window) |
| |
| def do_clear(window): |
| first, last = window.textobject.getfocus() |
| if first == last: |
| stdwin.fleep() # Nothing to clear |
| else: |
| window.textobject.replace('') |
| window.changed = 1 |
| fix_docsize(window) |
| |
| |
| def do_select_all(window): |
| window.textobject.setfocus(0, 0x7fffffff) # XXX Smaller on the Mac! |
| |
| |
| main() |