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.
Files changed (84) hide show
  1. tkinter_web/__init__.py +89 -0
  2. tkinter_web/__main__.py +69 -0
  3. tkinter_web/_tkinter.py +321 -0
  4. tkinter_web/_vendor/LICENSE-Python.txt +277 -0
  5. tkinter_web/_vendor/README.md +9 -0
  6. tkinter_web/_vendor/tkinter/__init__.py +4988 -0
  7. tkinter_web/_vendor/tkinter/__main__.py +7 -0
  8. tkinter_web/_vendor/tkinter/colorchooser.py +86 -0
  9. tkinter_web/_vendor/tkinter/commondialog.py +53 -0
  10. tkinter_web/_vendor/tkinter/constants.py +110 -0
  11. tkinter_web/_vendor/tkinter/dialog.py +49 -0
  12. tkinter_web/_vendor/tkinter/dnd.py +324 -0
  13. tkinter_web/_vendor/tkinter/filedialog.py +492 -0
  14. tkinter_web/_vendor/tkinter/font.py +239 -0
  15. tkinter_web/_vendor/tkinter/messagebox.py +146 -0
  16. tkinter_web/_vendor/tkinter/scrolledtext.py +56 -0
  17. tkinter_web/_vendor/tkinter/simpledialog.py +441 -0
  18. tkinter_web/_vendor/tkinter/ttk.py +1647 -0
  19. tkinter_web/app.py +358 -0
  20. tkinter_web/client/fonts/LICENSE-DejaVu.txt +47 -0
  21. tkinter_web/client/fonts/mono-bold-italic.woff2 +0 -0
  22. tkinter_web/client/fonts/mono-bold.woff2 +0 -0
  23. tkinter_web/client/fonts/mono-italic.woff2 +0 -0
  24. tkinter_web/client/fonts/mono.woff2 +0 -0
  25. tkinter_web/client/fonts/sans-bold-italic.woff2 +0 -0
  26. tkinter_web/client/fonts/sans-bold.woff2 +0 -0
  27. tkinter_web/client/fonts/sans-italic.woff2 +0 -0
  28. tkinter_web/client/fonts/sans.woff2 +0 -0
  29. tkinter_web/client/fonts/serif-bold-italic.woff2 +0 -0
  30. tkinter_web/client/fonts/serif-bold.woff2 +0 -0
  31. tkinter_web/client/fonts/serif-italic.woff2 +0 -0
  32. tkinter_web/client/fonts/serif.woff2 +0 -0
  33. tkinter_web/client/index.html +17 -0
  34. tkinter_web/client/js/bitmaps.js +82 -0
  35. tkinter_web/client/js/main.js +1427 -0
  36. tkinter_web/client/js/paint.js +694 -0
  37. tkinter_web/client/tkweb.css +342 -0
  38. tkinter_web/embed.py +204 -0
  39. tkinter_web/page.py +49 -0
  40. tkinter_web/tk/LICENSE-Tk.txt +40 -0
  41. tkinter_web/tk/__init__.py +11 -0
  42. tkinter_web/tk/_keysyms.py +547 -0
  43. tkinter_web/tk/_specs.py +1137 -0
  44. tkinter_web/tk/colors.json +1 -0
  45. tkinter_web/tk/dialogs.py +554 -0
  46. tkinter_web/tk/events.py +458 -0
  47. tkinter_web/tk/fontmetrics.json +1 -0
  48. tkinter_web/tk/fonts.py +467 -0
  49. tkinter_web/tk/geometry.py +1170 -0
  50. tkinter_web/tk/images.py +875 -0
  51. tkinter_web/tk/input.py +361 -0
  52. tkinter_web/tk/interp.py +1287 -0
  53. tkinter_web/tk/layout.py +192 -0
  54. tkinter_web/tk/options.py +368 -0
  55. tkinter_web/tk/render.py +258 -0
  56. tkinter_web/tk/tcl.py +262 -0
  57. tkinter_web/tk/ttk/__init__.py +16 -0
  58. tkinter_web/tk/ttk/style.py +357 -0
  59. tkinter_web/tk/ttk/treeview.py +817 -0
  60. tkinter_web/tk/ttk/widgets.py +1818 -0
  61. tkinter_web/tk/variables.py +152 -0
  62. tkinter_web/tk/widgets/__init__.py +89 -0
  63. tkinter_web/tk/widgets/base.py +255 -0
  64. tkinter_web/tk/widgets/buttons.py +452 -0
  65. tkinter_web/tk/widgets/canvas.py +1643 -0
  66. tkinter_web/tk/widgets/entry.py +752 -0
  67. tkinter_web/tk/widgets/frame.py +189 -0
  68. tkinter_web/tk/widgets/listbox.py +450 -0
  69. tkinter_web/tk/widgets/menu.py +723 -0
  70. tkinter_web/tk/widgets/message.py +85 -0
  71. tkinter_web/tk/widgets/panedwindow.py +249 -0
  72. tkinter_web/tk/widgets/scale.py +423 -0
  73. tkinter_web/tk/widgets/scrollbar.py +224 -0
  74. tkinter_web/tk/widgets/text.py +1546 -0
  75. tkinter_web/tk/winfo.py +147 -0
  76. tkinter_web/tk/wm.py +236 -0
  77. tkinter_web/transport/__init__.py +1 -0
  78. tkinter_web/transport/base.py +34 -0
  79. tkinter_web/transport/port.py +88 -0
  80. tkinter_web/transport/websocket.py +142 -0
  81. tkinter_web_backend-0.1.0.dist-info/METADATA +247 -0
  82. tkinter_web_backend-0.1.0.dist-info/RECORD +84 -0
  83. tkinter_web_backend-0.1.0.dist-info/WHEEL +4 -0
  84. tkinter_web_backend-0.1.0.dist-info/licenses/LICENSE +674 -0
@@ -0,0 +1,7 @@
1
+ """Main entry point"""
2
+
3
+ import sys
4
+ if sys.argv[0].endswith("__main__.py"):
5
+ sys.argv[0] = "python -m tkinter"
6
+ from . import _test as main
7
+ main()
@@ -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()