tkinter-web-backend 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tkinter_web/__init__.py +89 -0
- tkinter_web/__main__.py +69 -0
- tkinter_web/_tkinter.py +321 -0
- tkinter_web/_vendor/LICENSE-Python.txt +277 -0
- tkinter_web/_vendor/README.md +9 -0
- tkinter_web/_vendor/tkinter/__init__.py +4988 -0
- tkinter_web/_vendor/tkinter/__main__.py +7 -0
- tkinter_web/_vendor/tkinter/colorchooser.py +86 -0
- tkinter_web/_vendor/tkinter/commondialog.py +53 -0
- tkinter_web/_vendor/tkinter/constants.py +110 -0
- tkinter_web/_vendor/tkinter/dialog.py +49 -0
- tkinter_web/_vendor/tkinter/dnd.py +324 -0
- tkinter_web/_vendor/tkinter/filedialog.py +492 -0
- tkinter_web/_vendor/tkinter/font.py +239 -0
- tkinter_web/_vendor/tkinter/messagebox.py +146 -0
- tkinter_web/_vendor/tkinter/scrolledtext.py +56 -0
- tkinter_web/_vendor/tkinter/simpledialog.py +441 -0
- tkinter_web/_vendor/tkinter/ttk.py +1647 -0
- tkinter_web/app.py +358 -0
- tkinter_web/client/fonts/LICENSE-DejaVu.txt +47 -0
- tkinter_web/client/fonts/mono-bold-italic.woff2 +0 -0
- tkinter_web/client/fonts/mono-bold.woff2 +0 -0
- tkinter_web/client/fonts/mono-italic.woff2 +0 -0
- tkinter_web/client/fonts/mono.woff2 +0 -0
- tkinter_web/client/fonts/sans-bold-italic.woff2 +0 -0
- tkinter_web/client/fonts/sans-bold.woff2 +0 -0
- tkinter_web/client/fonts/sans-italic.woff2 +0 -0
- tkinter_web/client/fonts/sans.woff2 +0 -0
- tkinter_web/client/fonts/serif-bold-italic.woff2 +0 -0
- tkinter_web/client/fonts/serif-bold.woff2 +0 -0
- tkinter_web/client/fonts/serif-italic.woff2 +0 -0
- tkinter_web/client/fonts/serif.woff2 +0 -0
- tkinter_web/client/index.html +17 -0
- tkinter_web/client/js/bitmaps.js +82 -0
- tkinter_web/client/js/main.js +1427 -0
- tkinter_web/client/js/paint.js +694 -0
- tkinter_web/client/tkweb.css +342 -0
- tkinter_web/embed.py +204 -0
- tkinter_web/page.py +49 -0
- tkinter_web/tk/LICENSE-Tk.txt +40 -0
- tkinter_web/tk/__init__.py +11 -0
- tkinter_web/tk/_keysyms.py +547 -0
- tkinter_web/tk/_specs.py +1137 -0
- tkinter_web/tk/colors.json +1 -0
- tkinter_web/tk/dialogs.py +554 -0
- tkinter_web/tk/events.py +458 -0
- tkinter_web/tk/fontmetrics.json +1 -0
- tkinter_web/tk/fonts.py +467 -0
- tkinter_web/tk/geometry.py +1170 -0
- tkinter_web/tk/images.py +875 -0
- tkinter_web/tk/input.py +361 -0
- tkinter_web/tk/interp.py +1287 -0
- tkinter_web/tk/layout.py +192 -0
- tkinter_web/tk/options.py +368 -0
- tkinter_web/tk/render.py +258 -0
- tkinter_web/tk/tcl.py +262 -0
- tkinter_web/tk/ttk/__init__.py +16 -0
- tkinter_web/tk/ttk/style.py +357 -0
- tkinter_web/tk/ttk/treeview.py +817 -0
- tkinter_web/tk/ttk/widgets.py +1818 -0
- tkinter_web/tk/variables.py +152 -0
- tkinter_web/tk/widgets/__init__.py +89 -0
- tkinter_web/tk/widgets/base.py +255 -0
- tkinter_web/tk/widgets/buttons.py +452 -0
- tkinter_web/tk/widgets/canvas.py +1643 -0
- tkinter_web/tk/widgets/entry.py +752 -0
- tkinter_web/tk/widgets/frame.py +189 -0
- tkinter_web/tk/widgets/listbox.py +450 -0
- tkinter_web/tk/widgets/menu.py +723 -0
- tkinter_web/tk/widgets/message.py +85 -0
- tkinter_web/tk/widgets/panedwindow.py +249 -0
- tkinter_web/tk/widgets/scale.py +423 -0
- tkinter_web/tk/widgets/scrollbar.py +224 -0
- tkinter_web/tk/widgets/text.py +1546 -0
- tkinter_web/tk/winfo.py +147 -0
- tkinter_web/tk/wm.py +236 -0
- tkinter_web/transport/__init__.py +1 -0
- tkinter_web/transport/base.py +34 -0
- tkinter_web/transport/port.py +88 -0
- tkinter_web/transport/websocket.py +142 -0
- tkinter_web_backend-0.1.0.dist-info/METADATA +247 -0
- tkinter_web_backend-0.1.0.dist-info/RECORD +84 -0
- tkinter_web_backend-0.1.0.dist-info/WHEEL +4 -0
- tkinter_web_backend-0.1.0.dist-info/licenses/LICENSE +674 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# tk common color chooser dialogue
|
|
2
|
+
#
|
|
3
|
+
# this module provides an interface to the native color dialogue
|
|
4
|
+
# available in Tk 4.2 and newer.
|
|
5
|
+
#
|
|
6
|
+
# written by Fredrik Lundh, May 1997
|
|
7
|
+
#
|
|
8
|
+
# fixed initialcolor handling in August 1998
|
|
9
|
+
#
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
from tkinter.commondialog import Dialog
|
|
13
|
+
|
|
14
|
+
__all__ = ["Chooser", "askcolor"]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Chooser(Dialog):
|
|
18
|
+
"""Create a dialog for the tk_chooseColor command.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
master: The master widget for this dialog. If not provided,
|
|
22
|
+
defaults to options['parent'] (if defined).
|
|
23
|
+
options: Dictionary of options for the tk_chooseColor call.
|
|
24
|
+
initialcolor: Specifies the selected color when the
|
|
25
|
+
dialog is first displayed. This can be a tk color
|
|
26
|
+
string or a 3-tuple of ints in the range (0, 255)
|
|
27
|
+
for an RGB triplet.
|
|
28
|
+
parent: The parent window of the color dialog. The
|
|
29
|
+
color dialog is displayed on top of this.
|
|
30
|
+
title: A string for the title of the dialog box.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
command = "tk_chooseColor"
|
|
34
|
+
|
|
35
|
+
def _fixoptions(self):
|
|
36
|
+
"""Ensure initialcolor is a tk color string.
|
|
37
|
+
|
|
38
|
+
Convert initialcolor from a RGB triplet to a color string.
|
|
39
|
+
"""
|
|
40
|
+
try:
|
|
41
|
+
color = self.options["initialcolor"]
|
|
42
|
+
if isinstance(color, tuple):
|
|
43
|
+
# Assume an RGB triplet.
|
|
44
|
+
self.options["initialcolor"] = "#%02x%02x%02x" % color
|
|
45
|
+
except KeyError:
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
def _fixresult(self, widget, result):
|
|
49
|
+
"""Adjust result returned from call to tk_chooseColor.
|
|
50
|
+
|
|
51
|
+
Return both an RGB tuple of ints in the range (0, 255) and the
|
|
52
|
+
tk color string in the form #rrggbb.
|
|
53
|
+
"""
|
|
54
|
+
# Result can be many things: an empty tuple, an empty string, or
|
|
55
|
+
# a _tkinter.Tcl_Obj, so this somewhat weird check handles that.
|
|
56
|
+
if not result or not str(result):
|
|
57
|
+
return None, None # canceled
|
|
58
|
+
|
|
59
|
+
# To simplify application code, the color chooser returns
|
|
60
|
+
# an RGB tuple together with the Tk color string.
|
|
61
|
+
r, g, b = widget.winfo_rgb(result)
|
|
62
|
+
return (r//256, g//256, b//256), str(result)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
#
|
|
66
|
+
# convenience stuff
|
|
67
|
+
|
|
68
|
+
def askcolor(color=None, **options):
|
|
69
|
+
"""Display dialog window for selection of a color.
|
|
70
|
+
|
|
71
|
+
Convenience wrapper for the Chooser class. Displays the color
|
|
72
|
+
chooser dialog with color as the initial value.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
if color:
|
|
76
|
+
options = options.copy()
|
|
77
|
+
options["initialcolor"] = color
|
|
78
|
+
|
|
79
|
+
return Chooser(**options).show()
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# --------------------------------------------------------------------
|
|
83
|
+
# test stuff
|
|
84
|
+
|
|
85
|
+
if __name__ == "__main__":
|
|
86
|
+
print("color", askcolor())
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# base class for tk common dialogues
|
|
2
|
+
#
|
|
3
|
+
# this module provides a base class for accessing the common
|
|
4
|
+
# dialogues available in Tk 4.2 and newer. use filedialog,
|
|
5
|
+
# colorchooser, and messagebox to access the individual
|
|
6
|
+
# dialogs.
|
|
7
|
+
#
|
|
8
|
+
# written by Fredrik Lundh, May 1997
|
|
9
|
+
#
|
|
10
|
+
|
|
11
|
+
__all__ = ["Dialog"]
|
|
12
|
+
|
|
13
|
+
from tkinter import _get_temp_root, _destroy_temp_root
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Dialog:
|
|
17
|
+
|
|
18
|
+
command = None
|
|
19
|
+
|
|
20
|
+
def __init__(self, master=None, **options):
|
|
21
|
+
if master is None:
|
|
22
|
+
master = options.get('parent')
|
|
23
|
+
self.master = master
|
|
24
|
+
self.options = options
|
|
25
|
+
|
|
26
|
+
def _fixoptions(self):
|
|
27
|
+
pass # hook
|
|
28
|
+
|
|
29
|
+
def _fixresult(self, widget, result):
|
|
30
|
+
return result # hook
|
|
31
|
+
|
|
32
|
+
def show(self, **options):
|
|
33
|
+
|
|
34
|
+
# update instance options
|
|
35
|
+
for k, v in options.items():
|
|
36
|
+
self.options[k] = v
|
|
37
|
+
|
|
38
|
+
self._fixoptions()
|
|
39
|
+
|
|
40
|
+
master = self.master
|
|
41
|
+
if master is None:
|
|
42
|
+
master = _get_temp_root()
|
|
43
|
+
try:
|
|
44
|
+
self._test_callback(master) # The function below is replaced for some tests.
|
|
45
|
+
s = master.tk.call(self.command, *master._options(self.options))
|
|
46
|
+
s = self._fixresult(master, s)
|
|
47
|
+
finally:
|
|
48
|
+
_destroy_temp_root(master)
|
|
49
|
+
|
|
50
|
+
return s
|
|
51
|
+
|
|
52
|
+
def _test_callback(self, master):
|
|
53
|
+
pass
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Symbolic constants for Tk
|
|
2
|
+
|
|
3
|
+
# Booleans
|
|
4
|
+
NO=FALSE=OFF=0
|
|
5
|
+
YES=TRUE=ON=1
|
|
6
|
+
|
|
7
|
+
# -anchor and -sticky
|
|
8
|
+
N='n'
|
|
9
|
+
S='s'
|
|
10
|
+
W='w'
|
|
11
|
+
E='e'
|
|
12
|
+
NW='nw'
|
|
13
|
+
SW='sw'
|
|
14
|
+
NE='ne'
|
|
15
|
+
SE='se'
|
|
16
|
+
NS='ns'
|
|
17
|
+
EW='ew'
|
|
18
|
+
NSEW='nsew'
|
|
19
|
+
CENTER='center'
|
|
20
|
+
|
|
21
|
+
# -fill
|
|
22
|
+
NONE='none'
|
|
23
|
+
X='x'
|
|
24
|
+
Y='y'
|
|
25
|
+
BOTH='both'
|
|
26
|
+
|
|
27
|
+
# -side
|
|
28
|
+
LEFT='left'
|
|
29
|
+
TOP='top'
|
|
30
|
+
RIGHT='right'
|
|
31
|
+
BOTTOM='bottom'
|
|
32
|
+
|
|
33
|
+
# -relief
|
|
34
|
+
RAISED='raised'
|
|
35
|
+
SUNKEN='sunken'
|
|
36
|
+
FLAT='flat'
|
|
37
|
+
RIDGE='ridge'
|
|
38
|
+
GROOVE='groove'
|
|
39
|
+
SOLID = 'solid'
|
|
40
|
+
|
|
41
|
+
# -orient
|
|
42
|
+
HORIZONTAL='horizontal'
|
|
43
|
+
VERTICAL='vertical'
|
|
44
|
+
|
|
45
|
+
# -tabs
|
|
46
|
+
NUMERIC='numeric'
|
|
47
|
+
|
|
48
|
+
# -wrap
|
|
49
|
+
CHAR='char'
|
|
50
|
+
WORD='word'
|
|
51
|
+
|
|
52
|
+
# -align
|
|
53
|
+
BASELINE='baseline'
|
|
54
|
+
|
|
55
|
+
# -bordermode
|
|
56
|
+
INSIDE='inside'
|
|
57
|
+
OUTSIDE='outside'
|
|
58
|
+
|
|
59
|
+
# Special tags, marks and insert positions
|
|
60
|
+
SEL='sel'
|
|
61
|
+
SEL_FIRST='sel.first'
|
|
62
|
+
SEL_LAST='sel.last'
|
|
63
|
+
END='end'
|
|
64
|
+
INSERT='insert'
|
|
65
|
+
CURRENT='current'
|
|
66
|
+
ANCHOR='anchor'
|
|
67
|
+
ALL='all' # e.g. Canvas.delete(ALL)
|
|
68
|
+
|
|
69
|
+
# Text widget and button states
|
|
70
|
+
NORMAL='normal'
|
|
71
|
+
DISABLED='disabled'
|
|
72
|
+
ACTIVE='active'
|
|
73
|
+
# Canvas state
|
|
74
|
+
HIDDEN='hidden'
|
|
75
|
+
|
|
76
|
+
# Menu item types
|
|
77
|
+
CASCADE='cascade'
|
|
78
|
+
CHECKBUTTON='checkbutton'
|
|
79
|
+
COMMAND='command'
|
|
80
|
+
RADIOBUTTON='radiobutton'
|
|
81
|
+
SEPARATOR='separator'
|
|
82
|
+
|
|
83
|
+
# Selection modes for list boxes
|
|
84
|
+
SINGLE='single'
|
|
85
|
+
BROWSE='browse'
|
|
86
|
+
MULTIPLE='multiple'
|
|
87
|
+
EXTENDED='extended'
|
|
88
|
+
|
|
89
|
+
# Activestyle for list boxes
|
|
90
|
+
# NONE='none' is also valid
|
|
91
|
+
DOTBOX='dotbox'
|
|
92
|
+
UNDERLINE='underline'
|
|
93
|
+
|
|
94
|
+
# Various canvas styles
|
|
95
|
+
PIESLICE='pieslice'
|
|
96
|
+
CHORD='chord'
|
|
97
|
+
ARC='arc'
|
|
98
|
+
FIRST='first'
|
|
99
|
+
LAST='last'
|
|
100
|
+
BUTT='butt'
|
|
101
|
+
PROJECTING='projecting'
|
|
102
|
+
ROUND='round'
|
|
103
|
+
BEVEL='bevel'
|
|
104
|
+
MITER='miter'
|
|
105
|
+
|
|
106
|
+
# Arguments to xview/yview
|
|
107
|
+
MOVETO='moveto'
|
|
108
|
+
SCROLL='scroll'
|
|
109
|
+
UNITS='units'
|
|
110
|
+
PAGES='pages'
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# dialog.py -- Tkinter interface to the tk_dialog script.
|
|
2
|
+
|
|
3
|
+
from tkinter import _cnfmerge, Widget, TclError, Button, Pack
|
|
4
|
+
|
|
5
|
+
__all__ = ["Dialog"]
|
|
6
|
+
|
|
7
|
+
DIALOG_ICON = 'questhead'
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Dialog(Widget):
|
|
11
|
+
def __init__(self, master=None, cnf={}, **kw):
|
|
12
|
+
cnf = _cnfmerge((cnf, kw))
|
|
13
|
+
self.widgetName = '__dialog__'
|
|
14
|
+
self._setup(master, cnf)
|
|
15
|
+
self.num = self.tk.getint(
|
|
16
|
+
self.tk.call(
|
|
17
|
+
'tk_dialog', self._w,
|
|
18
|
+
cnf['title'], cnf['text'],
|
|
19
|
+
cnf['bitmap'], cnf['default'],
|
|
20
|
+
*cnf['strings']))
|
|
21
|
+
try: Widget.destroy(self)
|
|
22
|
+
except TclError: pass
|
|
23
|
+
|
|
24
|
+
def destroy(self): pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _test():
|
|
28
|
+
d = Dialog(None, {'title': 'File Modified',
|
|
29
|
+
'text':
|
|
30
|
+
'File "Python.h" has been modified'
|
|
31
|
+
' since the last time it was saved.'
|
|
32
|
+
' Do you want to save it before'
|
|
33
|
+
' exiting the application.',
|
|
34
|
+
'bitmap': DIALOG_ICON,
|
|
35
|
+
'default': 0,
|
|
36
|
+
'strings': ('Save File',
|
|
37
|
+
'Discard Changes',
|
|
38
|
+
'Return to Editor')})
|
|
39
|
+
print(d.num)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
if __name__ == '__main__':
|
|
43
|
+
t = Button(None, {'text': 'Test',
|
|
44
|
+
'command': _test,
|
|
45
|
+
Pack: {}})
|
|
46
|
+
q = Button(None, {'text': 'Quit',
|
|
47
|
+
'command': t.quit,
|
|
48
|
+
Pack: {}})
|
|
49
|
+
t.mainloop()
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
"""Drag-and-drop support for Tkinter.
|
|
2
|
+
|
|
3
|
+
This is very preliminary. I currently only support dnd *within* one
|
|
4
|
+
application, between different windows (or within the same window).
|
|
5
|
+
|
|
6
|
+
I am trying to make this as generic as possible -- not dependent on
|
|
7
|
+
the use of a particular widget or icon type, etc. I also hope that
|
|
8
|
+
this will work with Pmw.
|
|
9
|
+
|
|
10
|
+
To enable an object to be dragged, you must create an event binding
|
|
11
|
+
for it that starts the drag-and-drop process. Typically, you should
|
|
12
|
+
bind <ButtonPress> to a callback function that you write. The function
|
|
13
|
+
should call Tkdnd.dnd_start(source, event), where 'source' is the
|
|
14
|
+
object to be dragged, and 'event' is the event that invoked the call
|
|
15
|
+
(the argument to your callback function). Even though this is a class
|
|
16
|
+
instantiation, the returned instance should not be stored -- it will
|
|
17
|
+
be kept alive automatically for the duration of the drag-and-drop.
|
|
18
|
+
|
|
19
|
+
When a drag-and-drop is already in process for the Tk interpreter, the
|
|
20
|
+
call is *ignored*; this normally averts starting multiple simultaneous
|
|
21
|
+
dnd processes, e.g. because different button callbacks all
|
|
22
|
+
dnd_start().
|
|
23
|
+
|
|
24
|
+
The object is *not* necessarily a widget -- it can be any
|
|
25
|
+
application-specific object that is meaningful to potential
|
|
26
|
+
drag-and-drop targets.
|
|
27
|
+
|
|
28
|
+
Potential drag-and-drop targets are discovered as follows. Whenever
|
|
29
|
+
the mouse moves, and at the start and end of a drag-and-drop move, the
|
|
30
|
+
Tk widget directly under the mouse is inspected. This is the target
|
|
31
|
+
widget (not to be confused with the target object, yet to be
|
|
32
|
+
determined). If there is no target widget, there is no dnd target
|
|
33
|
+
object. If there is a target widget, and it has an attribute
|
|
34
|
+
dnd_accept, this should be a function (or any callable object). The
|
|
35
|
+
function is called as dnd_accept(source, event), where 'source' is the
|
|
36
|
+
object being dragged (the object passed to dnd_start() above), and
|
|
37
|
+
'event' is the most recent event object (generally a <Motion> event;
|
|
38
|
+
it can also be <ButtonPress> or <ButtonRelease>). If the dnd_accept()
|
|
39
|
+
function returns something other than None, this is the new dnd target
|
|
40
|
+
object. If dnd_accept() returns None, or if the target widget has no
|
|
41
|
+
dnd_accept attribute, the target widget's parent is considered as the
|
|
42
|
+
target widget, and the search for a target object is repeated from
|
|
43
|
+
there. If necessary, the search is repeated all the way up to the
|
|
44
|
+
root widget. If none of the target widgets can produce a target
|
|
45
|
+
object, there is no target object (the target object is None).
|
|
46
|
+
|
|
47
|
+
The target object thus produced, if any, is called the new target
|
|
48
|
+
object. It is compared with the old target object (or None, if there
|
|
49
|
+
was no old target widget). There are several cases ('source' is the
|
|
50
|
+
source object, and 'event' is the most recent event object):
|
|
51
|
+
|
|
52
|
+
- Both the old and new target objects are None. Nothing happens.
|
|
53
|
+
|
|
54
|
+
- The old and new target objects are the same object. Its method
|
|
55
|
+
dnd_motion(source, event) is called.
|
|
56
|
+
|
|
57
|
+
- The old target object was None, and the new target object is not
|
|
58
|
+
None. The new target object's method dnd_enter(source, event) is
|
|
59
|
+
called.
|
|
60
|
+
|
|
61
|
+
- The new target object is None, and the old target object is not
|
|
62
|
+
None. The old target object's method dnd_leave(source, event) is
|
|
63
|
+
called.
|
|
64
|
+
|
|
65
|
+
- The old and new target objects differ and neither is None. The old
|
|
66
|
+
target object's method dnd_leave(source, event), and then the new
|
|
67
|
+
target object's method dnd_enter(source, event) is called.
|
|
68
|
+
|
|
69
|
+
Once this is done, the new target object replaces the old one, and the
|
|
70
|
+
Tk mainloop proceeds. The return value of the methods mentioned above
|
|
71
|
+
is ignored; if they raise an exception, the normal exception handling
|
|
72
|
+
mechanisms take over.
|
|
73
|
+
|
|
74
|
+
The drag-and-drop processes can end in two ways: a final target object
|
|
75
|
+
is selected, or no final target object is selected. When a final
|
|
76
|
+
target object is selected, it will always have been notified of the
|
|
77
|
+
potential drop by a call to its dnd_enter() method, as described
|
|
78
|
+
above, and possibly one or more calls to its dnd_motion() method; its
|
|
79
|
+
dnd_leave() method has not been called since the last call to
|
|
80
|
+
dnd_enter(). The target is notified of the drop by a call to its
|
|
81
|
+
method dnd_commit(source, event).
|
|
82
|
+
|
|
83
|
+
If no final target object is selected, and there was an old target
|
|
84
|
+
object, its dnd_leave(source, event) method is called to complete the
|
|
85
|
+
dnd sequence.
|
|
86
|
+
|
|
87
|
+
Finally, the source object is notified that the drag-and-drop process
|
|
88
|
+
is over, by a call to source.dnd_end(target, event), specifying either
|
|
89
|
+
the selected target object, or None if no target object was selected.
|
|
90
|
+
The source object can use this to implement the commit action; this is
|
|
91
|
+
sometimes simpler than to do it in the target's dnd_commit(). The
|
|
92
|
+
target's dnd_commit() method could then simply be aliased to
|
|
93
|
+
dnd_leave().
|
|
94
|
+
|
|
95
|
+
At any time during a dnd sequence, the application can cancel the
|
|
96
|
+
sequence by calling the cancel() method on the object returned by
|
|
97
|
+
dnd_start(). This will call dnd_leave() if a target is currently
|
|
98
|
+
active; it will never call dnd_commit().
|
|
99
|
+
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
import tkinter
|
|
103
|
+
|
|
104
|
+
__all__ = ["dnd_start", "DndHandler"]
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# The factory function
|
|
108
|
+
|
|
109
|
+
def dnd_start(source, event):
|
|
110
|
+
h = DndHandler(source, event)
|
|
111
|
+
if h.root is not None:
|
|
112
|
+
return h
|
|
113
|
+
else:
|
|
114
|
+
return None
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# The class that does the work
|
|
118
|
+
|
|
119
|
+
class DndHandler:
|
|
120
|
+
|
|
121
|
+
root = None
|
|
122
|
+
|
|
123
|
+
def __init__(self, source, event):
|
|
124
|
+
if event.num > 5:
|
|
125
|
+
return
|
|
126
|
+
root = event.widget._root()
|
|
127
|
+
try:
|
|
128
|
+
root.__dnd
|
|
129
|
+
return # Don't start recursive dnd
|
|
130
|
+
except AttributeError:
|
|
131
|
+
root.__dnd = self
|
|
132
|
+
self.root = root
|
|
133
|
+
self.source = source
|
|
134
|
+
self.target = None
|
|
135
|
+
self.initial_button = button = event.num
|
|
136
|
+
self.initial_widget = widget = event.widget
|
|
137
|
+
self.release_pattern = "<B%d-ButtonRelease-%d>" % (button, button)
|
|
138
|
+
self.save_cursor = widget['cursor'] or ""
|
|
139
|
+
widget.bind(self.release_pattern, self.on_release)
|
|
140
|
+
widget.bind("<Motion>", self.on_motion)
|
|
141
|
+
widget['cursor'] = "hand2"
|
|
142
|
+
|
|
143
|
+
def __del__(self):
|
|
144
|
+
root = self.root
|
|
145
|
+
self.root = None
|
|
146
|
+
if root is not None:
|
|
147
|
+
try:
|
|
148
|
+
del root.__dnd
|
|
149
|
+
except AttributeError:
|
|
150
|
+
pass
|
|
151
|
+
|
|
152
|
+
def on_motion(self, event):
|
|
153
|
+
x, y = event.x_root, event.y_root
|
|
154
|
+
target_widget = self.initial_widget.winfo_containing(x, y)
|
|
155
|
+
source = self.source
|
|
156
|
+
new_target = None
|
|
157
|
+
while target_widget is not None:
|
|
158
|
+
try:
|
|
159
|
+
attr = target_widget.dnd_accept
|
|
160
|
+
except AttributeError:
|
|
161
|
+
pass
|
|
162
|
+
else:
|
|
163
|
+
new_target = attr(source, event)
|
|
164
|
+
if new_target is not None:
|
|
165
|
+
break
|
|
166
|
+
target_widget = target_widget.master
|
|
167
|
+
old_target = self.target
|
|
168
|
+
if old_target is new_target:
|
|
169
|
+
if old_target is not None:
|
|
170
|
+
old_target.dnd_motion(source, event)
|
|
171
|
+
else:
|
|
172
|
+
if old_target is not None:
|
|
173
|
+
self.target = None
|
|
174
|
+
old_target.dnd_leave(source, event)
|
|
175
|
+
if new_target is not None:
|
|
176
|
+
new_target.dnd_enter(source, event)
|
|
177
|
+
self.target = new_target
|
|
178
|
+
|
|
179
|
+
def on_release(self, event):
|
|
180
|
+
self.finish(event, 1)
|
|
181
|
+
|
|
182
|
+
def cancel(self, event=None):
|
|
183
|
+
self.finish(event, 0)
|
|
184
|
+
|
|
185
|
+
def finish(self, event, commit=0):
|
|
186
|
+
target = self.target
|
|
187
|
+
source = self.source
|
|
188
|
+
widget = self.initial_widget
|
|
189
|
+
root = self.root
|
|
190
|
+
try:
|
|
191
|
+
del root.__dnd
|
|
192
|
+
self.initial_widget.unbind(self.release_pattern)
|
|
193
|
+
self.initial_widget.unbind("<Motion>")
|
|
194
|
+
widget['cursor'] = self.save_cursor
|
|
195
|
+
self.target = self.source = self.initial_widget = self.root = None
|
|
196
|
+
if target is not None:
|
|
197
|
+
if commit:
|
|
198
|
+
target.dnd_commit(source, event)
|
|
199
|
+
else:
|
|
200
|
+
target.dnd_leave(source, event)
|
|
201
|
+
finally:
|
|
202
|
+
source.dnd_end(target, event)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
# ----------------------------------------------------------------------
|
|
206
|
+
# The rest is here for testing and demonstration purposes only!
|
|
207
|
+
|
|
208
|
+
class Icon:
|
|
209
|
+
|
|
210
|
+
def __init__(self, name):
|
|
211
|
+
self.name = name
|
|
212
|
+
self.canvas = self.label = self.id = None
|
|
213
|
+
|
|
214
|
+
def attach(self, canvas, x=10, y=10):
|
|
215
|
+
if canvas is self.canvas:
|
|
216
|
+
self.canvas.coords(self.id, x, y)
|
|
217
|
+
return
|
|
218
|
+
if self.canvas is not None:
|
|
219
|
+
self.detach()
|
|
220
|
+
if canvas is None:
|
|
221
|
+
return
|
|
222
|
+
label = tkinter.Label(canvas, text=self.name,
|
|
223
|
+
borderwidth=2, relief="raised")
|
|
224
|
+
id = canvas.create_window(x, y, window=label, anchor="nw")
|
|
225
|
+
self.canvas = canvas
|
|
226
|
+
self.label = label
|
|
227
|
+
self.id = id
|
|
228
|
+
label.bind("<ButtonPress>", self.press)
|
|
229
|
+
|
|
230
|
+
def detach(self):
|
|
231
|
+
canvas = self.canvas
|
|
232
|
+
if canvas is None:
|
|
233
|
+
return
|
|
234
|
+
id = self.id
|
|
235
|
+
label = self.label
|
|
236
|
+
self.canvas = self.label = self.id = None
|
|
237
|
+
canvas.delete(id)
|
|
238
|
+
label.destroy()
|
|
239
|
+
|
|
240
|
+
def press(self, event):
|
|
241
|
+
if dnd_start(self, event):
|
|
242
|
+
# where the pointer is relative to the label widget:
|
|
243
|
+
self.x_off = event.x
|
|
244
|
+
self.y_off = event.y
|
|
245
|
+
# where the widget is relative to the canvas:
|
|
246
|
+
self.x_orig, self.y_orig = self.canvas.coords(self.id)
|
|
247
|
+
|
|
248
|
+
def move(self, event):
|
|
249
|
+
x, y = self.where(self.canvas, event)
|
|
250
|
+
self.canvas.coords(self.id, x, y)
|
|
251
|
+
|
|
252
|
+
def putback(self):
|
|
253
|
+
self.canvas.coords(self.id, self.x_orig, self.y_orig)
|
|
254
|
+
|
|
255
|
+
def where(self, canvas, event):
|
|
256
|
+
# where the corner of the canvas is relative to the screen:
|
|
257
|
+
x_org = canvas.winfo_rootx()
|
|
258
|
+
y_org = canvas.winfo_rooty()
|
|
259
|
+
# where the pointer is relative to the canvas widget:
|
|
260
|
+
x = event.x_root - x_org
|
|
261
|
+
y = event.y_root - y_org
|
|
262
|
+
# compensate for initial pointer offset
|
|
263
|
+
return x - self.x_off, y - self.y_off
|
|
264
|
+
|
|
265
|
+
def dnd_end(self, target, event):
|
|
266
|
+
pass
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
class Tester:
|
|
270
|
+
|
|
271
|
+
def __init__(self, root):
|
|
272
|
+
self.top = tkinter.Toplevel(root)
|
|
273
|
+
self.canvas = tkinter.Canvas(self.top, width=100, height=100)
|
|
274
|
+
self.canvas.pack(fill="both", expand=1)
|
|
275
|
+
self.canvas.dnd_accept = self.dnd_accept
|
|
276
|
+
|
|
277
|
+
def dnd_accept(self, source, event):
|
|
278
|
+
return self
|
|
279
|
+
|
|
280
|
+
def dnd_enter(self, source, event):
|
|
281
|
+
self.canvas.focus_set() # Show highlight border
|
|
282
|
+
x, y = source.where(self.canvas, event)
|
|
283
|
+
x1, y1, x2, y2 = source.canvas.bbox(source.id)
|
|
284
|
+
dx, dy = x2-x1, y2-y1
|
|
285
|
+
self.dndid = self.canvas.create_rectangle(x, y, x+dx, y+dy)
|
|
286
|
+
self.dnd_motion(source, event)
|
|
287
|
+
|
|
288
|
+
def dnd_motion(self, source, event):
|
|
289
|
+
x, y = source.where(self.canvas, event)
|
|
290
|
+
x1, y1, x2, y2 = self.canvas.bbox(self.dndid)
|
|
291
|
+
self.canvas.move(self.dndid, x-x1, y-y1)
|
|
292
|
+
|
|
293
|
+
def dnd_leave(self, source, event):
|
|
294
|
+
self.top.focus_set() # Hide highlight border
|
|
295
|
+
self.canvas.delete(self.dndid)
|
|
296
|
+
self.dndid = None
|
|
297
|
+
|
|
298
|
+
def dnd_commit(self, source, event):
|
|
299
|
+
self.dnd_leave(source, event)
|
|
300
|
+
x, y = source.where(self.canvas, event)
|
|
301
|
+
source.attach(self.canvas, x, y)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def test():
|
|
305
|
+
root = tkinter.Tk()
|
|
306
|
+
root.geometry("+1+1")
|
|
307
|
+
tkinter.Button(command=root.quit, text="Quit").pack()
|
|
308
|
+
t1 = Tester(root)
|
|
309
|
+
t1.top.geometry("+1+60")
|
|
310
|
+
t2 = Tester(root)
|
|
311
|
+
t2.top.geometry("+120+60")
|
|
312
|
+
t3 = Tester(root)
|
|
313
|
+
t3.top.geometry("+240+60")
|
|
314
|
+
i1 = Icon("ICON1")
|
|
315
|
+
i2 = Icon("ICON2")
|
|
316
|
+
i3 = Icon("ICON3")
|
|
317
|
+
i1.attach(t1.canvas)
|
|
318
|
+
i2.attach(t2.canvas)
|
|
319
|
+
i3.attach(t3.canvas)
|
|
320
|
+
root.mainloop()
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
if __name__ == '__main__':
|
|
324
|
+
test()
|