ยปCore Development>Code coverage>Lib/idlelib/multicall.py

Python code coverage for Lib/idlelib/multicall.py

#countcontent
1n/a"""
2n/aMultiCall - a class which inherits its methods from a Tkinter widget (Text, for
3n/aexample), but enables multiple calls of functions per virtual event - all
4n/amatching events will be called, not only the most specific one. This is done
5n/aby wrapping the event functions - event_add, event_delete and event_info.
6n/aMultiCall recognizes only a subset of legal event sequences. Sequences which
7n/aare not recognized are treated by the original Tk handling mechanism. A
8n/amore-specific event will be called before a less-specific event.
9n/a
10n/aThe recognized sequences are complete one-event sequences (no emacs-style
11n/aCtrl-X Ctrl-C, no shortcuts like <3>), for all types of events.
12n/aKey/Button Press/Release events can have modifiers.
13n/aThe recognized modifiers are Shift, Control, Option and Command for Mac, and
14n/aControl, Alt, Shift, Meta/M for other platforms.
15n/a
16n/aFor all events which were handled by MultiCall, a new member is added to the
17n/aevent instance passed to the binded functions - mc_type. This is one of the
18n/aevent type constants defined in this module (such as MC_KEYPRESS).
19n/aFor Key/Button events (which are handled by MultiCall and may receive
20n/amodifiers), another member is added - mc_state. This member gives the state
21n/aof the recognized modifiers, as a combination of the modifier constants
22n/aalso defined in this module (for example, MC_SHIFT).
23n/aUsing these members is absolutely portable.
24n/a
25n/aThe order by which events are called is defined by these rules:
26n/a1. A more-specific event will be called before a less-specific event.
27n/a2. A recently-binded event will be called before a previously-binded event,
28n/a unless this conflicts with the first rule.
29n/aEach function will be called at most once for each event.
30n/a"""
31n/aimport re
32n/aimport sys
33n/a
34n/aimport tkinter
35n/a
36n/a# the event type constants, which define the meaning of mc_type
37n/aMC_KEYPRESS=0; MC_KEYRELEASE=1; MC_BUTTONPRESS=2; MC_BUTTONRELEASE=3;
38n/aMC_ACTIVATE=4; MC_CIRCULATE=5; MC_COLORMAP=6; MC_CONFIGURE=7;
39n/aMC_DEACTIVATE=8; MC_DESTROY=9; MC_ENTER=10; MC_EXPOSE=11; MC_FOCUSIN=12;
40n/aMC_FOCUSOUT=13; MC_GRAVITY=14; MC_LEAVE=15; MC_MAP=16; MC_MOTION=17;
41n/aMC_MOUSEWHEEL=18; MC_PROPERTY=19; MC_REPARENT=20; MC_UNMAP=21; MC_VISIBILITY=22;
42n/a# the modifier state constants, which define the meaning of mc_state
43n/aMC_SHIFT = 1<<0; MC_CONTROL = 1<<2; MC_ALT = 1<<3; MC_META = 1<<5
44n/aMC_OPTION = 1<<6; MC_COMMAND = 1<<7
45n/a
46n/a# define the list of modifiers, to be used in complex event types.
47n/aif sys.platform == "darwin":
48n/a _modifiers = (("Shift",), ("Control",), ("Option",), ("Command",))
49n/a _modifier_masks = (MC_SHIFT, MC_CONTROL, MC_OPTION, MC_COMMAND)
50n/aelse:
51n/a _modifiers = (("Control",), ("Alt",), ("Shift",), ("Meta", "M"))
52n/a _modifier_masks = (MC_CONTROL, MC_ALT, MC_SHIFT, MC_META)
53n/a
54n/a# a dictionary to map a modifier name into its number
55n/a_modifier_names = dict([(name, number)
56n/a for number in range(len(_modifiers))
57n/a for name in _modifiers[number]])
58n/a
59n/a# In 3.4, if no shell window is ever open, the underlying Tk widget is
60n/a# destroyed before .__del__ methods here are called. The following
61n/a# is used to selectively ignore shutdown exceptions to avoid
62n/a# 'Exception ignored' messages. See http://bugs.python.org/issue20167
63n/aAPPLICATION_GONE = "application has been destroyed"
64n/a
65n/a# A binder is a class which binds functions to one type of event. It has two
66n/a# methods: bind and unbind, which get a function and a parsed sequence, as
67n/a# returned by _parse_sequence(). There are two types of binders:
68n/a# _SimpleBinder handles event types with no modifiers and no detail.
69n/a# No Python functions are called when no events are binded.
70n/a# _ComplexBinder handles event types with modifiers and a detail.
71n/a# A Python function is called each time an event is generated.
72n/a
73n/aclass _SimpleBinder:
74n/a def __init__(self, type, widget, widgetinst):
75n/a self.type = type
76n/a self.sequence = '<'+_types[type][0]+'>'
77n/a self.widget = widget
78n/a self.widgetinst = widgetinst
79n/a self.bindedfuncs = []
80n/a self.handlerid = None
81n/a
82n/a def bind(self, triplet, func):
83n/a if not self.handlerid:
84n/a def handler(event, l = self.bindedfuncs, mc_type = self.type):
85n/a event.mc_type = mc_type
86n/a wascalled = {}
87n/a for i in range(len(l)-1, -1, -1):
88n/a func = l[i]
89n/a if func not in wascalled:
90n/a wascalled[func] = True
91n/a r = func(event)
92n/a if r:
93n/a return r
94n/a self.handlerid = self.widget.bind(self.widgetinst,
95n/a self.sequence, handler)
96n/a self.bindedfuncs.append(func)
97n/a
98n/a def unbind(self, triplet, func):
99n/a self.bindedfuncs.remove(func)
100n/a if not self.bindedfuncs:
101n/a self.widget.unbind(self.widgetinst, self.sequence, self.handlerid)
102n/a self.handlerid = None
103n/a
104n/a def __del__(self):
105n/a if self.handlerid:
106n/a try:
107n/a self.widget.unbind(self.widgetinst, self.sequence,
108n/a self.handlerid)
109n/a except tkinter.TclError as e:
110n/a if not APPLICATION_GONE in e.args[0]:
111n/a raise
112n/a
113n/a# An int in range(1 << len(_modifiers)) represents a combination of modifiers
114n/a# (if the least significant bit is on, _modifiers[0] is on, and so on).
115n/a# _state_subsets gives for each combination of modifiers, or *state*,
116n/a# a list of the states which are a subset of it. This list is ordered by the
117n/a# number of modifiers is the state - the most specific state comes first.
118n/a_states = range(1 << len(_modifiers))
119n/a_state_names = [''.join(m[0]+'-'
120n/a for i, m in enumerate(_modifiers)
121n/a if (1 << i) & s)
122n/a for s in _states]
123n/a
124n/adef expand_substates(states):
125n/a '''For each item of states return a list containing all combinations of
126n/a that item with individual bits reset, sorted by the number of set bits.
127n/a '''
128n/a def nbits(n):
129n/a "number of bits set in n base 2"
130n/a nb = 0
131n/a while n:
132n/a n, rem = divmod(n, 2)
133n/a nb += rem
134n/a return nb
135n/a statelist = []
136n/a for state in states:
137n/a substates = list(set(state & x for x in states))
138n/a substates.sort(key=nbits, reverse=True)
139n/a statelist.append(substates)
140n/a return statelist
141n/a
142n/a_state_subsets = expand_substates(_states)
143n/a
144n/a# _state_codes gives for each state, the portable code to be passed as mc_state
145n/a_state_codes = []
146n/afor s in _states:
147n/a r = 0
148n/a for i in range(len(_modifiers)):
149n/a if (1 << i) & s:
150n/a r |= _modifier_masks[i]
151n/a _state_codes.append(r)
152n/a
153n/aclass _ComplexBinder:
154n/a # This class binds many functions, and only unbinds them when it is deleted.
155n/a # self.handlerids is the list of seqs and ids of binded handler functions.
156n/a # The binded functions sit in a dictionary of lists of lists, which maps
157n/a # a detail (or None) and a state into a list of functions.
158n/a # When a new detail is discovered, handlers for all the possible states
159n/a # are binded.
160n/a
161n/a def __create_handler(self, lists, mc_type, mc_state):
162n/a def handler(event, lists = lists,
163n/a mc_type = mc_type, mc_state = mc_state,
164n/a ishandlerrunning = self.ishandlerrunning,
165n/a doafterhandler = self.doafterhandler):
166n/a ishandlerrunning[:] = [True]
167n/a event.mc_type = mc_type
168n/a event.mc_state = mc_state
169n/a wascalled = {}
170n/a r = None
171n/a for l in lists:
172n/a for i in range(len(l)-1, -1, -1):
173n/a func = l[i]
174n/a if func not in wascalled:
175n/a wascalled[func] = True
176n/a r = l[i](event)
177n/a if r:
178n/a break
179n/a if r:
180n/a break
181n/a ishandlerrunning[:] = []
182n/a # Call all functions in doafterhandler and remove them from list
183n/a for f in doafterhandler:
184n/a f()
185n/a doafterhandler[:] = []
186n/a if r:
187n/a return r
188n/a return handler
189n/a
190n/a def __init__(self, type, widget, widgetinst):
191n/a self.type = type
192n/a self.typename = _types[type][0]
193n/a self.widget = widget
194n/a self.widgetinst = widgetinst
195n/a self.bindedfuncs = {None: [[] for s in _states]}
196n/a self.handlerids = []
197n/a # we don't want to change the lists of functions while a handler is
198n/a # running - it will mess up the loop and anyway, we usually want the
199n/a # change to happen from the next event. So we have a list of functions
200n/a # for the handler to run after it finishes calling the binded functions.
201n/a # It calls them only once.
202n/a # ishandlerrunning is a list. An empty one means no, otherwise - yes.
203n/a # this is done so that it would be mutable.
204n/a self.ishandlerrunning = []
205n/a self.doafterhandler = []
206n/a for s in _states:
207n/a lists = [self.bindedfuncs[None][i] for i in _state_subsets[s]]
208n/a handler = self.__create_handler(lists, type, _state_codes[s])
209n/a seq = '<'+_state_names[s]+self.typename+'>'
210n/a self.handlerids.append((seq, self.widget.bind(self.widgetinst,
211n/a seq, handler)))
212n/a
213n/a def bind(self, triplet, func):
214n/a if triplet[2] not in self.bindedfuncs:
215n/a self.bindedfuncs[triplet[2]] = [[] for s in _states]
216n/a for s in _states:
217n/a lists = [ self.bindedfuncs[detail][i]
218n/a for detail in (triplet[2], None)
219n/a for i in _state_subsets[s] ]
220n/a handler = self.__create_handler(lists, self.type,
221n/a _state_codes[s])
222n/a seq = "<%s%s-%s>"% (_state_names[s], self.typename, triplet[2])
223n/a self.handlerids.append((seq, self.widget.bind(self.widgetinst,
224n/a seq, handler)))
225n/a doit = lambda: self.bindedfuncs[triplet[2]][triplet[0]].append(func)
226n/a if not self.ishandlerrunning:
227n/a doit()
228n/a else:
229n/a self.doafterhandler.append(doit)
230n/a
231n/a def unbind(self, triplet, func):
232n/a doit = lambda: self.bindedfuncs[triplet[2]][triplet[0]].remove(func)
233n/a if not self.ishandlerrunning:
234n/a doit()
235n/a else:
236n/a self.doafterhandler.append(doit)
237n/a
238n/a def __del__(self):
239n/a for seq, id in self.handlerids:
240n/a try:
241n/a self.widget.unbind(self.widgetinst, seq, id)
242n/a except tkinter.TclError as e:
243n/a if not APPLICATION_GONE in e.args[0]:
244n/a raise
245n/a
246n/a# define the list of event types to be handled by MultiEvent. the order is
247n/a# compatible with the definition of event type constants.
248n/a_types = (
249n/a ("KeyPress", "Key"), ("KeyRelease",), ("ButtonPress", "Button"),
250n/a ("ButtonRelease",), ("Activate",), ("Circulate",), ("Colormap",),
251n/a ("Configure",), ("Deactivate",), ("Destroy",), ("Enter",), ("Expose",),
252n/a ("FocusIn",), ("FocusOut",), ("Gravity",), ("Leave",), ("Map",),
253n/a ("Motion",), ("MouseWheel",), ("Property",), ("Reparent",), ("Unmap",),
254n/a ("Visibility",),
255n/a)
256n/a
257n/a# which binder should be used for every event type?
258n/a_binder_classes = (_ComplexBinder,) * 4 + (_SimpleBinder,) * (len(_types)-4)
259n/a
260n/a# A dictionary to map a type name into its number
261n/a_type_names = dict([(name, number)
262n/a for number in range(len(_types))
263n/a for name in _types[number]])
264n/a
265n/a_keysym_re = re.compile(r"^\w+$")
266n/a_button_re = re.compile(r"^[1-5]$")
267n/adef _parse_sequence(sequence):
268n/a """Get a string which should describe an event sequence. If it is
269n/a successfully parsed as one, return a tuple containing the state (as an int),
270n/a the event type (as an index of _types), and the detail - None if none, or a
271n/a string if there is one. If the parsing is unsuccessful, return None.
272n/a """
273n/a if not sequence or sequence[0] != '<' or sequence[-1] != '>':
274n/a return None
275n/a words = sequence[1:-1].split('-')
276n/a modifiers = 0
277n/a while words and words[0] in _modifier_names:
278n/a modifiers |= 1 << _modifier_names[words[0]]
279n/a del words[0]
280n/a if words and words[0] in _type_names:
281n/a type = _type_names[words[0]]
282n/a del words[0]
283n/a else:
284n/a return None
285n/a if _binder_classes[type] is _SimpleBinder:
286n/a if modifiers or words:
287n/a return None
288n/a else:
289n/a detail = None
290n/a else:
291n/a # _ComplexBinder
292n/a if type in [_type_names[s] for s in ("KeyPress", "KeyRelease")]:
293n/a type_re = _keysym_re
294n/a else:
295n/a type_re = _button_re
296n/a
297n/a if not words:
298n/a detail = None
299n/a elif len(words) == 1 and type_re.match(words[0]):
300n/a detail = words[0]
301n/a else:
302n/a return None
303n/a
304n/a return modifiers, type, detail
305n/a
306n/adef _triplet_to_sequence(triplet):
307n/a if triplet[2]:
308n/a return '<'+_state_names[triplet[0]]+_types[triplet[1]][0]+'-'+ \
309n/a triplet[2]+'>'
310n/a else:
311n/a return '<'+_state_names[triplet[0]]+_types[triplet[1]][0]+'>'
312n/a
313n/a_multicall_dict = {}
314n/adef MultiCallCreator(widget):
315n/a """Return a MultiCall class which inherits its methods from the
316n/a given widget class (for example, Tkinter.Text). This is used
317n/a instead of a templating mechanism.
318n/a """
319n/a if widget in _multicall_dict:
320n/a return _multicall_dict[widget]
321n/a
322n/a class MultiCall (widget):
323n/a assert issubclass(widget, tkinter.Misc)
324n/a
325n/a def __init__(self, *args, **kwargs):
326n/a widget.__init__(self, *args, **kwargs)
327n/a # a dictionary which maps a virtual event to a tuple with:
328n/a # 0. the function binded
329n/a # 1. a list of triplets - the sequences it is binded to
330n/a self.__eventinfo = {}
331n/a self.__binders = [_binder_classes[i](i, widget, self)
332n/a for i in range(len(_types))]
333n/a
334n/a def bind(self, sequence=None, func=None, add=None):
335n/a #print("bind(%s, %s, %s)" % (sequence, func, add),
336n/a # file=sys.__stderr__)
337n/a if type(sequence) is str and len(sequence) > 2 and \
338n/a sequence[:2] == "<<" and sequence[-2:] == ">>":
339n/a if sequence in self.__eventinfo:
340n/a ei = self.__eventinfo[sequence]
341n/a if ei[0] is not None:
342n/a for triplet in ei[1]:
343n/a self.__binders[triplet[1]].unbind(triplet, ei[0])
344n/a ei[0] = func
345n/a if ei[0] is not None:
346n/a for triplet in ei[1]:
347n/a self.__binders[triplet[1]].bind(triplet, func)
348n/a else:
349n/a self.__eventinfo[sequence] = [func, []]
350n/a return widget.bind(self, sequence, func, add)
351n/a
352n/a def unbind(self, sequence, funcid=None):
353n/a if type(sequence) is str and len(sequence) > 2 and \
354n/a sequence[:2] == "<<" and sequence[-2:] == ">>" and \
355n/a sequence in self.__eventinfo:
356n/a func, triplets = self.__eventinfo[sequence]
357n/a if func is not None:
358n/a for triplet in triplets:
359n/a self.__binders[triplet[1]].unbind(triplet, func)
360n/a self.__eventinfo[sequence][0] = None
361n/a return widget.unbind(self, sequence, funcid)
362n/a
363n/a def event_add(self, virtual, *sequences):
364n/a #print("event_add(%s, %s)" % (repr(virtual), repr(sequences)),
365n/a # file=sys.__stderr__)
366n/a if virtual not in self.__eventinfo:
367n/a self.__eventinfo[virtual] = [None, []]
368n/a
369n/a func, triplets = self.__eventinfo[virtual]
370n/a for seq in sequences:
371n/a triplet = _parse_sequence(seq)
372n/a if triplet is None:
373n/a #print("Tkinter event_add(%s)" % seq, file=sys.__stderr__)
374n/a widget.event_add(self, virtual, seq)
375n/a else:
376n/a if func is not None:
377n/a self.__binders[triplet[1]].bind(triplet, func)
378n/a triplets.append(triplet)
379n/a
380n/a def event_delete(self, virtual, *sequences):
381n/a if virtual not in self.__eventinfo:
382n/a return
383n/a func, triplets = self.__eventinfo[virtual]
384n/a for seq in sequences:
385n/a triplet = _parse_sequence(seq)
386n/a if triplet is None:
387n/a #print("Tkinter event_delete: %s" % seq, file=sys.__stderr__)
388n/a widget.event_delete(self, virtual, seq)
389n/a else:
390n/a if func is not None:
391n/a self.__binders[triplet[1]].unbind(triplet, func)
392n/a triplets.remove(triplet)
393n/a
394n/a def event_info(self, virtual=None):
395n/a if virtual is None or virtual not in self.__eventinfo:
396n/a return widget.event_info(self, virtual)
397n/a else:
398n/a return tuple(map(_triplet_to_sequence,
399n/a self.__eventinfo[virtual][1])) + \
400n/a widget.event_info(self, virtual)
401n/a
402n/a def __del__(self):
403n/a for virtual in self.__eventinfo:
404n/a func, triplets = self.__eventinfo[virtual]
405n/a if func:
406n/a for triplet in triplets:
407n/a try:
408n/a self.__binders[triplet[1]].unbind(triplet, func)
409n/a except tkinter.TclError as e:
410n/a if not APPLICATION_GONE in e.args[0]:
411n/a raise
412n/a
413n/a _multicall_dict[widget] = MultiCall
414n/a return MultiCall
415n/a
416n/a
417n/adef _multi_call(parent): # htest #
418n/a top = tkinter.Toplevel(parent)
419n/a top.title("Test MultiCall")
420n/a x, y = map(int, parent.geometry().split('+')[1:])
421n/a top.geometry("+%d+%d" % (x, y + 175))
422n/a text = MultiCallCreator(tkinter.Text)(top)
423n/a text.pack()
424n/a def bindseq(seq, n=[0]):
425n/a def handler(event):
426n/a print(seq)
427n/a text.bind("<<handler%d>>"%n[0], handler)
428n/a text.event_add("<<handler%d>>"%n[0], seq)
429n/a n[0] += 1
430n/a bindseq("<Key>")
431n/a bindseq("<Control-Key>")
432n/a bindseq("<Alt-Key-a>")
433n/a bindseq("<Control-Key-a>")
434n/a bindseq("<Alt-Control-Key-a>")
435n/a bindseq("<Key-b>")
436n/a bindseq("<Control-Button-1>")
437n/a bindseq("<Button-2>")
438n/a bindseq("<Alt-Button-1>")
439n/a bindseq("<FocusOut>")
440n/a bindseq("<Enter>")
441n/a bindseq("<Leave>")
442n/a
443n/aif __name__ == "__main__":
444n/a from idlelib.idle_test.htest import run
445n/a run(_multi_call)