def onFontList(self):
self.fonts.append(self.fonts[0]) # pick next font in list
del self.fonts[0] # resizes the text area
self.text.config(font=self.fonts[0])
def onColorList(self):
self.colors.append(self.colors[0]) # pick next color in list
del self.colors[0] # move current to end
self.text.config(fg=self.colors[0]['fg'], bg=self.colors[0]['bg'])
def onPickFg(self):
self.pickColor('fg') # added on 10/02/00
def onPickBg(self): # select arbitrary color
self.pickColor('bg') # in standard color dialog
def pickColor(self, part): # this is too easy
(triple, hexstr) = askcolor()
if hexstr:
self.text.config(**{part: hexstr})
def onInfo(self):
"""
pop-up dialog giving text statistics and cursor location;
caveat (2.1): Tk insert position column counts a tab as one
character: translate to next multiple of 8 to match visual?
"""
text = self.getAllText() # added on 5/3/00 in 15 mins
bytes = len(text) # words uses a simple guess:
lines = len(text.split('\n')) # any separated by whitespace
words = len(text.split()) # 3.x: bytes is really chars
index = self.text.index(INSERT) # str is unicode code points
where = tuple(index.split('.'))
showinfo('PyEdit Information',
'Current location:\n\n' +
'line:\t%s\ncolumn:\t%s\n\n' % where +
'File text statistics:\n\n' +
'chars:\t%d\nlines:\t%d\nwords:\t%d\n' % (bytes, lines, words))
def onClone(self, makewindow=True):
"""
open a new edit window without changing one already open (onNew);
inherits quit and other behavior of the window that it clones;
2.1: subclass must redefine/replace this if makes its own popup,
else this creates a bogus extra window here which will be empty;
"""
if not makewindow:
new = None # assume class makes its own window
else:
new = Toplevel() # a new edit window in same process
myclass = self.__class__ # instance's (lowest) class object
myclass(new) # attach/run instance of my class
def onRunCode(self, parallelmode=True):
"""
run Python code being edited--not an IDE, but handy;
tries to run in file's dir, not cwd (may be PP4E root);
inputs and adds command-line arguments for script files;
code's stdin/out/err = editor's start window, if any:
run with a console window to see code's print outputs;
but parallelmode uses start to open a DOS box for I/O;
module search path will include '.' dir where started;
in non-file mode, code's Tk root may be PyEdit's window;
subprocess or multiprocessing modules may work here too;
2.1: fixed to use base file name after chdir, not path;
2.1: use StartArgs to allow args in file mode on Windows;
2.1: run an update() after 1st dialog else 2nd dialog
sometimes does not appear in rare cases;
"""
def askcmdargs():
return askstring('PyEdit', 'Commandline arguments?') or ''
from PP4E.launchmodes import System, Start, StartArgs, Fork
filemode = False
thefile = str(self.getFileName())
if os.path.exists(thefile):
filemode = askyesno('PyEdit', 'Run from file?')
self.update() # 2.1: run update()
if not filemode: # run text string
cmdargs = askcmdargs()
namespace = {'__name__': '__main__'} # run as top-level
sys.argv = [thefile] + cmdargs.split() # could use threads
exec(self.getAllText() + '\n', namespace) # exceptions ignored
elif self.text_edit_modified(): # 2.0: changed test
showerror('PyEdit', 'Text changed: you must save before run')
else:
cmdargs = askcmdargs()
mycwd = os.getcwd() # cwd may be root
dirname, filename = os.path.split(thefile) # get dir, base
os.chdir(dirname or mycwd) # cd for filenames
thecmd = filename + ' ' + cmdargs # 2.1: not theFile
if not parallelmode: # run as file
System(thecmd, thecmd)() # block editor
else:
if sys.platform[:3] == 'win': # spawn in parallel
run = StartArgs if cmdargs else Start # 2.1: support args
run(thecmd, thecmd)() # or always Spawn
else:
Fork(thecmd, thecmd)() # spawn in parallel
os.chdir(mycwd) # go back to my dir
def onPickFont(self):
"""
2.0 non-modal font spec dialog
2.1: pass per-dialog inputs to callback, may be > 1 font dialog open
"""
from PP4E.Gui.ShellGui.formrows import makeFormRow
popup = Toplevel(self)
popup.title('PyEdit - font')
var1 = makeFormRow(popup, label='Family', browse=False)
var2 = makeFormRow(popup, label='Size', browse=False)
var3 = makeFormRow(popup, label='Style', browse=False)
var1.set('courier')
var2.set('12') # suggested vals
var3.set('bold italic') # see pick list for valid inputs
Button(popup, text='Apply', command=
lambda: self.onDoFont(var1.get(), var2.get(), var3.get())).pack()
def onDoFont(self, family, size, style):
try:
self.text.config(font=(family, int(size), style))
except:
showerror('PyEdit', 'Bad font specification')
############################################################################
# Utilities, useful outside this class
############################################################################
def isEmpty(self):
return not self.getAllText()
def getAllText(self):
return self.text.get('1.0', END+'-1c') # extract text as str string
def setAllText(self, text):
"""
caller: call self.update() first if just packed, else the
initial position may be at line 2, not line 1 (2.1; Tk bug?)
"""
self.text.delete('1.0', END) # store text string in widget
self.text.insert(END, text) # or '1.0'; text=bytes or str
self.text.mark_set(INSERT, '1.0') # move insert point to top
self.text.see(INSERT) # scroll to top, insert set
def clearAllText(self):
self.text.delete('1.0', END) # clear text in widget
def getFileName(self):
return self.currfile
def setFileName(self, name): # see also: onGoto(linenum)
self.currfile = name # for save
self.filelabel.config(text=str(name))
def setKnownEncoding(self, encoding='utf-8'): # 2.1: for saves if inserted
self.knownEncoding = encoding # else saves use config, ask?
def setBg(self, color):
self.text.config(bg=color) # to set manually from code
def setFg(self, color):
self.text.config(fg=color) # 'black', hexstring
def setFont(self, font):
self.text.config(font=font) # ('family', size, 'style')
def setHeight(self, lines): # default = 24h x 80w
self.text.config(height=lines) # may also be from textCongif.py
def setWidth(self, chars):
self.text.config(width=chars)
def clearModified(self):
self.text.edit_modified(0) # clear modified flag
def isModified(self):
return self.text_edit_modified() # changed since last reset?
def help(self):
showinfo('About PyEdit', helptext % ((Version,)*2))
################################################################################
# Ready-to-use editor classes
# mixes in a GuiMaker Frame subclass which builds menu and toolbars
#
# these classes are common use cases, but other configurations are possible;
# call TextEditorMain().mainloop() to start PyEdit as a standalone program;
# redefine/extend onQuit in a subclass to catch exit or destroy (see PyView);
# caveat: could use windows.py for icons, but quit protocol is custom here.
################################################################################
#-------------------------------------------------------------------------------
# 2.1: on quit(), don't silently exit entire app if any other changed edit
# windows are open in the process - changes would be lost because all other
# windows are closed too, including multiple Tk editor parents; uses a list
# to keep track of all PyEdit window instances open in process; this may be
# too broad (if we destroy() instead of quit(), need only check children
# of parent being destroyed), but better to err on side of being too inclusive;
# onQuit moved here because varies per window type and is not present for all;
#
# assumes a TextEditorMainPopup is never a parent to other editor windows -
# Toplevel children are destroyed with their parents; this does not address
# closes outside the scope of PyEdit classes here (tkinter quit is available
# on every widget, and any widget type may be a Toplevel parent!); client is
# responsible for checking for editor content changes in all uncovered cases;
# note that tkinter's bind event won't help here, because its callback
# cannot run GUI operations such as text change tests and fetches - see the
# book and destroyer.py for more details on this event;
#-------------------------------------------------------------------------------
###################################
# when text editor owns the window
###################################
class TextEditorMain(TextEditor, GuiMakerWindowMenu):
"""
main PyEdit windows that quit() to exit app on a Quit in GUI, and build
a menu on a window; parent may be default Tk, explicit Tk, or Toplevel:
parent must be a window, and probably should be a Tk so this isn't silently
destroyed and closed with a parent; all main PyEdit windows check all other
PyEdit windows open in the process for changes on a Quit in the GUI, since
a quit() here will exit the entire app; the editor's frame need not occupy
entire window (may have other parts: see PyView), but its Quit ends program;
onQuit is run for Quit in toolbar or File menu, as well as window border X;
"""
def __init__(self, parent=None, loadFirst='', loadEncode=''):
# editor fills whole parent window
GuiMaker.__init__(self, parent) # use main window menus
TextEditor.__init__(self, loadFirst, loadEncode) # GuiMaker frame packs self
self.master.title('PyEdit ' + Version) # title, wm X if standalone
self.master.iconname('PyEdit')
self.master.protocol('WM_DELETE_WINDOW', self.onQuit)
TextEditor.editwindows.append(self)
def onQuit(self): # on a Quit request in the GUI
close = not self.text_edit_modified() # check self, ask?, check others
if not close:
close = askyesno('PyEdit', 'Text changed: quit and discard changes?')
if close:
windows = TextEditor.editwindows
changed = [w for w in windows if w != self and w.text_edit_modified()]
if not changed:
GuiMaker.quit(self) # quit ends entire app regardless of widget type
else:
numchange = len(changed)
verify = '%s other edit window%s changed: quit and discard anyhow?'
verify = verify % (numchange, 's' if numchange > 1 else '')
if askyesno('PyEdit', verify):
GuiMaker.quit(self)
class TextEditorMainPopup(TextEditor, GuiMakerWindowMenu):
"""
popup PyEdit windows that destroy() to close only self on a Quit in GUI,
and build a menu on a window; makes own Toplevel parent, which is child
to default Tk (for None) or other passed-in window or widget (e.g., a frame);
adds to list so will be checked for changes if any PyEdit main window quits;
if any PyEdit main windows will be created, parent of this should also be a
PyEdit main window's parent so this is not closed silently while being tracked;
onQuit is run for Quit in toolbar or File menu, as well as window border X;
"""
def __init__(self, parent=None, loadFirst='', winTitle='', loadEncode=''):
# create own window
self.popup = Toplevel(parent)
GuiMaker.__init__(self, self.popup) # use main window menus
TextEditor.__init__(self, loadFirst, loadEncode) # a frame in a new popup
assert self.master == self.popup
self.popup.title('PyEdit ' + Version + winTitle)
self.popup.iconname('PyEdit')
self.popup.protocol('WM_DELETE_WINDOW', self.onQuit)
TextEditor.editwindows.append(self)
def onQuit(self):
close = not self.text_edit_modified()
if not close:
close = askyesno('PyEdit', 'Text changed: quit and discard changes?')
if close:
self.popup.destroy() # kill this window only
TextEditor.editwindows.remove(self) # (plus any child windows)
def onClone(self):
TextEditor.onClone(self, makewindow=False) # I make my own pop-up
#########################################
# when editor embedded in another window
#########################################
class TextEditorComponent(TextEditor, GuiMakerFrameMenu):
"""
attached PyEdit component frames with full menu/toolbar options,
which run a destroy() on a Quit in the GUI to erase self only;
a Quit in the GUI verifies if any changes in self (only) here;
does not intercept window manager border X: doesn't own window;
does not add self to changes tracking list: part of larger app;
"""
def __init__(self, parent=None, loadFirst='', loadEncode=''):
# use Frame-based menus
GuiMaker.__init__(self, parent) # all menus, buttons on
TextEditor.__init__(self, loadFirst, loadEncode) # GuiMaker must init 1st
def onQuit(self):
close = not self.text_edit_modified()
if not close:
close = askyesno('PyEdit', 'Text changed: quit and discard changes?')
if close:
self.destroy() # erase self Frame but do not quit enclosing app
class TextEditorComponentMinimal(TextEditor, GuiMakerFrameMenu):
"""
attached PyEdit component frames without Quit and File menu options;
on startup, removes Quit from toolbar, and either deletes File menu
or disables all its items (possibly hackish, but sufficient); menu and
toolbar structures are per-instance data: changes do not impact others;
Quit in GUI never occurs, because it is removed from available options;
"""
def __init__(self, parent=None, loadFirst='', deleteFile=True, loadEncode=''):
self.deleteFile = deleteFile
GuiMaker.__init__(self, parent) # GuiMaker frame packs self
TextEditor.__init__(self, loadFirst, loadEncode) # TextEditor adds middle