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