mirror of https://github.com/python/cpython
Move menu/key binding code from Bindings.py to EditorWindow.py,
with changed APIs -- it makes much more sense there. Also add a new feature: if the first character of a menu label is a '!', it gets a checkbox. Checkboxes are bound to Boolean Tcl variables that can be accessed through the new getvar/setvar/getrawvar API; the variable is named after the event to which the menu is bound.
This commit is contained in:
parent
85ef9dce9f
commit
07ec896707
|
@ -7,7 +7,6 @@
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import string
|
import string
|
||||||
import re
|
|
||||||
from keydefs import *
|
from keydefs import *
|
||||||
|
|
||||||
menudefs = [
|
menudefs = [
|
||||||
|
@ -42,9 +41,9 @@ menudefs = [
|
||||||
]),
|
]),
|
||||||
('debug', [
|
('debug', [
|
||||||
('_Go to file/line', '<<goto-file-line>>'),
|
('_Go to file/line', '<<goto-file-line>>'),
|
||||||
('_Open stack viewer', '<<open-stack-viewer>>'),
|
('_Stack viewer', '<<open-stack-viewer>>'),
|
||||||
('_Debugger toggle', '<<toggle-debugger>>'),
|
('!_Debugger', '<<toggle-debugger>>'),
|
||||||
('_JIT Stack viewer toggle', '<<toggle-jit-stack-viewer>>' ),
|
('!_Auto-open stack viewer', '<<toggle-jit-stack-viewer>>' ),
|
||||||
]),
|
]),
|
||||||
('help', [
|
('help', [
|
||||||
('_Help...', '<<help>>'),
|
('_Help...', '<<help>>'),
|
||||||
|
@ -53,62 +52,7 @@ menudefs = [
|
||||||
]),
|
]),
|
||||||
]
|
]
|
||||||
|
|
||||||
def prepstr(s):
|
|
||||||
# Helper to extract the underscore from a string,
|
|
||||||
# e.g. prepstr("Co_py") returns (2, "Copy").
|
|
||||||
i = string.find(s, '_')
|
|
||||||
if i >= 0:
|
|
||||||
s = s[:i] + s[i+1:]
|
|
||||||
return i, s
|
|
||||||
|
|
||||||
keynames = {
|
|
||||||
'bracketleft': '[',
|
|
||||||
'bracketright': ']',
|
|
||||||
'slash': '/',
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_accelerator(keydefs, event):
|
|
||||||
keylist = keydefs.get(event)
|
|
||||||
if not keylist:
|
|
||||||
return ""
|
|
||||||
s = keylist[0]
|
|
||||||
s = re.sub(r"-[a-z]\b", lambda m: string.upper(m.group()), s)
|
|
||||||
s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
|
|
||||||
s = re.sub("Key-", "", s)
|
|
||||||
s = re.sub("Control-", "Ctrl-", s)
|
|
||||||
s = re.sub("-", "+", s)
|
|
||||||
s = re.sub("><", " ", s)
|
|
||||||
s = re.sub("<", "", s)
|
|
||||||
s = re.sub(">", "", s)
|
|
||||||
return s
|
|
||||||
|
|
||||||
if sys.platform == 'win32':
|
if sys.platform == 'win32':
|
||||||
default_keydefs = windows_keydefs
|
default_keydefs = windows_keydefs
|
||||||
else:
|
else:
|
||||||
default_keydefs = unix_keydefs
|
default_keydefs = unix_keydefs
|
||||||
|
|
||||||
def apply_bindings(text, keydefs=default_keydefs):
|
|
||||||
text.keydefs = keydefs
|
|
||||||
for event, keylist in keydefs.items():
|
|
||||||
if keylist:
|
|
||||||
apply(text.event_add, (event,) + tuple(keylist))
|
|
||||||
|
|
||||||
def fill_menus(text, menudict, defs=menudefs, keydefs=default_keydefs):
|
|
||||||
# Fill the menus for the given text widget. The menudict argument is
|
|
||||||
# a dictionary containing the menus, keyed by their lowercased name.
|
|
||||||
# Menus that are absent or None are ignored.
|
|
||||||
for mname, itemlist in defs:
|
|
||||||
menu = menudict.get(mname)
|
|
||||||
if not menu:
|
|
||||||
continue
|
|
||||||
for item in itemlist:
|
|
||||||
if not item:
|
|
||||||
menu.add_separator()
|
|
||||||
else:
|
|
||||||
label, event = item
|
|
||||||
underline, label = prepstr(label)
|
|
||||||
accelerator = get_accelerator(keydefs, event)
|
|
||||||
def command(text=text, event=event):
|
|
||||||
text.event_generate(event)
|
|
||||||
menu.add_command(label=label, underline=underline,
|
|
||||||
command=command, accelerator=accelerator)
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import string
|
import string
|
||||||
|
import re
|
||||||
import imp
|
import imp
|
||||||
from Tkinter import *
|
from Tkinter import *
|
||||||
import tkSimpleDialog
|
import tkSimpleDialog
|
||||||
|
@ -93,7 +94,7 @@ class EditorWindow:
|
||||||
background="white", wrap="none")
|
background="white", wrap="none")
|
||||||
|
|
||||||
self.createmenubar()
|
self.createmenubar()
|
||||||
self.Bindings.apply_bindings(text)
|
self.apply_bindings()
|
||||||
|
|
||||||
self.top.protocol("WM_DELETE_WINDOW", self.close)
|
self.top.protocol("WM_DELETE_WINDOW", self.close)
|
||||||
self.top.bind("<<close-window>>", self.close_event)
|
self.top.bind("<<close-window>>", self.close_event)
|
||||||
|
@ -172,15 +173,19 @@ class EditorWindow:
|
||||||
|
|
||||||
def createmenubar(self):
|
def createmenubar(self):
|
||||||
mbar = self.menubar
|
mbar = self.menubar
|
||||||
self.menudict = mdict = {}
|
self.menudict = menudict = {}
|
||||||
for name, label in self.menu_specs:
|
for name, label in self.menu_specs:
|
||||||
underline, label = self.Bindings.prepstr(label)
|
underline, label = prepstr(label)
|
||||||
mdict[name] = menu = Menu(mbar, name=name)
|
menudict[name] = menu = Menu(mbar, name=name)
|
||||||
mbar.add_cascade(label=label, menu=menu, underline=underline)
|
mbar.add_cascade(label=label, menu=menu, underline=underline)
|
||||||
self.Bindings.fill_menus(self.text, mdict)
|
self.fill_menus()
|
||||||
|
|
||||||
def postwindowsmenu(self):
|
def postwindowsmenu(self):
|
||||||
# Only called when Windows menu exists
|
# Only called when Windows menu exists
|
||||||
|
# XXX Actually, this Just-In-Time updating interferes
|
||||||
|
# XXX badly with the tear-off feature. It would be better
|
||||||
|
# XXX to update all Windows menus whenever the list of windows
|
||||||
|
# XXX changes.
|
||||||
menu = self.menudict['windows']
|
menu = self.menudict['windows']
|
||||||
end = menu.index("end")
|
end = menu.index("end")
|
||||||
if end is None:
|
if end is None:
|
||||||
|
@ -477,7 +482,7 @@ class EditorWindow:
|
||||||
if hasattr(ins, kdname):
|
if hasattr(ins, kdname):
|
||||||
keydefs.update(getattr(ins, kdname))
|
keydefs.update(getattr(ins, kdname))
|
||||||
if keydefs:
|
if keydefs:
|
||||||
self.Bindings.apply_bindings(self.text, keydefs)
|
self.apply_bindings(keydefs)
|
||||||
for vevent in keydefs.keys():
|
for vevent in keydefs.keys():
|
||||||
methodname = string.replace(vevent, "-", "_")
|
methodname = string.replace(vevent, "-", "_")
|
||||||
while methodname[:1] == '<':
|
while methodname[:1] == '<':
|
||||||
|
@ -488,10 +493,104 @@ class EditorWindow:
|
||||||
if hasattr(ins, methodname):
|
if hasattr(ins, methodname):
|
||||||
self.text.bind(vevent, getattr(ins, methodname))
|
self.text.bind(vevent, getattr(ins, methodname))
|
||||||
if hasattr(ins, "menudefs"):
|
if hasattr(ins, "menudefs"):
|
||||||
self.Bindings.fill_menus(self.text, self. menudict,
|
self.fill_menus(ins.menudefs, keydefs)
|
||||||
ins.menudefs, keydefs)
|
|
||||||
return ins
|
return ins
|
||||||
|
|
||||||
|
def apply_bindings(self, keydefs=None):
|
||||||
|
if keydefs is None:
|
||||||
|
keydefs = self.Bindings.default_keydefs
|
||||||
|
text = self.text
|
||||||
|
text.keydefs = keydefs
|
||||||
|
for event, keylist in keydefs.items():
|
||||||
|
if keylist:
|
||||||
|
apply(text.event_add, (event,) + tuple(keylist))
|
||||||
|
|
||||||
|
def fill_menus(self, defs=None, keydefs=None):
|
||||||
|
# Fill the menus.
|
||||||
|
# Menus that are absent or None in self.menudict are ignored.
|
||||||
|
if defs is None:
|
||||||
|
defs = self.Bindings.menudefs
|
||||||
|
if keydefs is None:
|
||||||
|
keydefs = self.Bindings.default_keydefs
|
||||||
|
menudict = self.menudict
|
||||||
|
text = self.text
|
||||||
|
for mname, itemlist in defs:
|
||||||
|
menu = menudict.get(mname)
|
||||||
|
if not menu:
|
||||||
|
continue
|
||||||
|
for item in itemlist:
|
||||||
|
if not item:
|
||||||
|
menu.add_separator()
|
||||||
|
else:
|
||||||
|
label, event = item
|
||||||
|
checkbutton = (label[:1] == '!')
|
||||||
|
if checkbutton:
|
||||||
|
label = label[1:]
|
||||||
|
underline, label = prepstr(label)
|
||||||
|
accelerator = get_accelerator(keydefs, event)
|
||||||
|
def command(text=text, event=event):
|
||||||
|
text.event_generate(event)
|
||||||
|
if checkbutton:
|
||||||
|
var = self.getrawvar(event, BooleanVar)
|
||||||
|
menu.add_checkbutton(label=label, underline=underline,
|
||||||
|
command=command, accelerator=accelerator,
|
||||||
|
variable=var)
|
||||||
|
else:
|
||||||
|
menu.add_command(label=label, underline=underline,
|
||||||
|
command=command, accelerator=accelerator)
|
||||||
|
|
||||||
|
def getvar(self, name):
|
||||||
|
var = self.getrawvar(name)
|
||||||
|
if var:
|
||||||
|
return var.get()
|
||||||
|
|
||||||
|
def setvar(self, name, value, vartype=None):
|
||||||
|
var = self.getrawvar(name, vartype)
|
||||||
|
if var:
|
||||||
|
var.set(value)
|
||||||
|
|
||||||
|
def getrawvar(self, name, vartype=None):
|
||||||
|
key = ".VARS."
|
||||||
|
vars = self.menudict.get(key)
|
||||||
|
if not vars and vartype:
|
||||||
|
self.menudict[key] = vars = {}
|
||||||
|
if vars is not None:
|
||||||
|
var = vars.get(name)
|
||||||
|
if not var and vartype:
|
||||||
|
vars[name] = var = vartype(self.text)
|
||||||
|
return var
|
||||||
|
|
||||||
|
|
||||||
|
def prepstr(s):
|
||||||
|
# Helper to extract the underscore from a string,
|
||||||
|
# e.g. prepstr("Co_py") returns (2, "Copy").
|
||||||
|
i = string.find(s, '_')
|
||||||
|
if i >= 0:
|
||||||
|
s = s[:i] + s[i+1:]
|
||||||
|
return i, s
|
||||||
|
|
||||||
|
|
||||||
|
keynames = {
|
||||||
|
'bracketleft': '[',
|
||||||
|
'bracketright': ']',
|
||||||
|
'slash': '/',
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_accelerator(keydefs, event):
|
||||||
|
keylist = keydefs.get(event)
|
||||||
|
if not keylist:
|
||||||
|
return ""
|
||||||
|
s = keylist[0]
|
||||||
|
s = re.sub(r"-[a-z]\b", lambda m: string.upper(m.group()), s)
|
||||||
|
s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
|
||||||
|
s = re.sub("Key-", "", s)
|
||||||
|
s = re.sub("Control-", "Ctrl-", s)
|
||||||
|
s = re.sub("-", "+", s)
|
||||||
|
s = re.sub("><", " ", s)
|
||||||
|
s = re.sub("<", "", s)
|
||||||
|
s = re.sub(">", "", s)
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
def fixwordbreaks(root):
|
def fixwordbreaks(root):
|
||||||
# Make sure that Tk's double-click and next/previous word
|
# Make sure that Tk's double-click and next/previous word
|
||||||
|
|
|
@ -217,11 +217,11 @@ class ModifiedInterpreter(InteractiveInterpreter):
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
self.showtraceback()
|
self.showtraceback()
|
||||||
if self.tkconsole.jit_stack_view:
|
if self.tkconsole.getvar("<<toggle-jit-stack-viewer>>"):
|
||||||
self.tkconsole.open_stack_viewer()
|
self.tkconsole.open_stack_viewer()
|
||||||
except:
|
except:
|
||||||
self.showtraceback()
|
self.showtraceback()
|
||||||
if self.tkconsole.jit_stack_view:
|
if self.tkconsole.getvar("<<toggle-jit-stack-viewer>>"):
|
||||||
self.tkconsole.open_stack_viewer()
|
self.tkconsole.open_stack_viewer()
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
|
@ -289,17 +289,20 @@ class PyShell(OutputWindow):
|
||||||
tkMessageBox.showerror("Don't debug now",
|
tkMessageBox.showerror("Don't debug now",
|
||||||
"You can only toggle the debugger when idle",
|
"You can only toggle the debugger when idle",
|
||||||
master=self.text)
|
master=self.text)
|
||||||
|
self.set_debugger_indicator()
|
||||||
return "break"
|
return "break"
|
||||||
db = self.interp.getdebugger()
|
|
||||||
if db:
|
|
||||||
self.close_debugger()
|
|
||||||
else:
|
else:
|
||||||
self.open_debugger()
|
db = self.interp.getdebugger()
|
||||||
|
if db:
|
||||||
jit_stack_view = 0
|
self.close_debugger()
|
||||||
|
else:
|
||||||
|
self.open_debugger()
|
||||||
|
|
||||||
|
def set_debugger_indicator(self):
|
||||||
|
db = self.interp.getdebugger()
|
||||||
|
self.setvar("<<toggle-debugger>>", not not db)
|
||||||
def toggle_jit_stack_viewer( self, event=None):
|
def toggle_jit_stack_viewer( self, event=None):
|
||||||
self.jit_stack_view = not self.jit_stack_view
|
pass # All we need is the variable
|
||||||
|
|
||||||
def close_debugger(self):
|
def close_debugger(self):
|
||||||
db = self.interp.getdebugger()
|
db = self.interp.getdebugger()
|
||||||
|
@ -310,12 +313,14 @@ class PyShell(OutputWindow):
|
||||||
self.console.write("[DEBUG OFF]\n")
|
self.console.write("[DEBUG OFF]\n")
|
||||||
sys.ps1 = ">>> "
|
sys.ps1 = ">>> "
|
||||||
self.showprompt()
|
self.showprompt()
|
||||||
|
self.set_debugger_indicator()
|
||||||
|
|
||||||
def open_debugger(self):
|
def open_debugger(self):
|
||||||
import Debugger
|
import Debugger
|
||||||
self.interp.setdebugger(Debugger.Debugger(self))
|
self.interp.setdebugger(Debugger.Debugger(self))
|
||||||
sys.ps1 = "[DEBUG ON]\n>>> "
|
sys.ps1 = "[DEBUG ON]\n>>> "
|
||||||
self.showprompt()
|
self.showprompt()
|
||||||
|
self.set_debugger_indicator()
|
||||||
|
|
||||||
def beginexecuting(self):
|
def beginexecuting(self):
|
||||||
# Helper for ModifiedInterpreter
|
# Helper for ModifiedInterpreter
|
||||||
|
@ -335,8 +340,8 @@ class PyShell(OutputWindow):
|
||||||
if self.executing:
|
if self.executing:
|
||||||
# XXX Need to ask a question here
|
# XXX Need to ask a question here
|
||||||
if not tkMessageBox.askokcancel(
|
if not tkMessageBox.askokcancel(
|
||||||
"Cancel?",
|
"Kill?",
|
||||||
"The program is still running; do you want to cancel it?",
|
"The program is still running; do you want to kill it?",
|
||||||
default="ok",
|
default="ok",
|
||||||
master=self.text):
|
master=self.text):
|
||||||
return "cancel"
|
return "cancel"
|
||||||
|
|
Loading…
Reference in New Issue