#! /usr/local/bin/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()