357 lines
8.4 KiB
Python
Executable File
357 lines
8.4 KiB
Python
Executable File
#! /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()
|