diff --git a/Doc/library/tkinter.dnd.rst b/Doc/library/tkinter.dnd.rst index 6c11c739e1fa11..0d72dfeb5fbd7c 100644 --- a/Doc/library/tkinter.dnd.rst +++ b/Doc/library/tkinter.dnd.rst @@ -1,16 +1,18 @@ -:mod:`tkinter.dnd` --- Drag and drop support -============================================ +:mod:`tkinter.dnd` --- Deprecated drag and drop support +======================================================= .. module:: tkinter.dnd :platform: Tk :synopsis: Tkinter drag-and-drop interface + :deprecated: **Source code:** :source:`Lib/tkinter/dnd.py` --------------- +.. deprecated:: 3.10 + The :mod:`tkinter.dnd` module is deprecated in favour of the TkDND bindings + in the main :mod:`tkinter` module. -.. note:: This is experimental and due to be deprecated when it is replaced - with the Tk DND. +-------------- The :mod:`tkinter.dnd` module provides drag-and-drop support for objects within a single application, within the same window or between windows. To enable an diff --git a/Doc/library/tkinter.rst b/Doc/library/tkinter.rst index 2dc44ad36a7f73..e112a6614a3e44 100644 --- a/Doc/library/tkinter.rst +++ b/Doc/library/tkinter.rst @@ -131,8 +131,8 @@ Other modules that provide Tk support include: Basic dialogs and convenience functions. :mod:`tkinter.dnd` - Drag-and-drop support for :mod:`tkinter`. This is experimental and should - become deprecated when it is replaced with the Tk DND. + Deprecated drag-and-drop support for :mod:`tkinter` + (see the :ref:`tkinter-tkdnd` section) :mod:`turtle` Turtle graphics in a Tk window. @@ -305,6 +305,583 @@ The legal values of *someOptions* is action dependent. Some actions, like command, would need arguments to specify what range of text to delete. +.. _tkinter-tkdnd: + +TkDND Support +------------- + +:mod:`tkinter` has support for a native, platform specific drag and drop +mechanism, through `Tkdnd `_, with the user +able to register widgets as drag sources or drop targets. + +On Windows, TkDND is packaged with +Tcl/Tk so should work "out-of-the-box" with a standard installation. On +other platforms, it will need to be installed seperately. + +.. note:: As a result of this functionality, the :mod:`tkinter.dnd` module is + now deprecated. + +.. versionadded:: 3.10 + +Functions +^^^^^^^^^ + +.. function:: Tk.load_dnd(dnd_path=None) + + This command will load the TkDND library and is called when ``Tk`` class is + instantiated. However, if the library is installed in a different + directory, this method will need to be called manually to load the library. + If the TkDND library has been found, ``True`` wil be returned, otherwise + ``False`` will be returned. + + If the TkDND library has already been successfully loaded, ``True`` will be + returned and no action taken. If Tk has not been loaded for the + interpreter (a call was made to ``tkinter.Tcl`` rather than ``tkinter.Tk``), + the value returned will always be ``False`` and no action taken. + + .. note:: Calling this method on one ``Tk`` instance will not affect any + others (i.e. the TkDND install directory will need to be declared to every + ``Tk`` instance where DND capabilities are required). + +.. function:: Misc.dnd_loaded() + + This function returns whether the TkDND library has been successfully loaded + for this ``Tk`` instance. + +.. function:: Misc.drop_target_register(types=()) + + This command will register the widget as a drop target (a widget than can + accept a drop action). An optional type-list can be provided, which + contains one or more types that the widget will accept during a drop + action. + + .. note:: This command can be executed multiple times on the same widget. + +.. function:: Misc.drop_target_unregister() + + This command will stop the widget from being a drop target. Thus, it will + stop receiving events related to drop operations. + +.. function:: Misc.drag_source_register(types=(), mouse_buttons=1) + + This command will register the widget as a drag source. A drag source is a + widget than can start a drag action. When the widget is registered as a + drag source, an optional type-list can be provided. This type list can + contain one or more types that the widget will provide during a drag + action. However, this type list is indicative/informative and the widget + *can* initiate a drag action with a type not in the list. + + Finally, ``mouse_buttons`` is one or more mouse buttons that can be used + for starting the drag action. It can have any of the values: + + * 1 - Left mouse button + * 2 - Middle mouse button (wheel) + * 3 - Right mouse button + + Multiple mouse buttons can be specified as a list/tuple of values. + + .. note:: This command can be executed multiple times on the same widget. + +.. function:: Misc.drag_source_unregister() + + This command will stop the widget from being a drag source. Thus, it will + stop receiving events related to drag operations. + +.. function:: Misc.platform_specific_types(types) + + This command will accept a list of types that can contain platform + independent or platform specific types. A new list will be returned, where + each platform independent type in type-list will be substituted by one or + more platform specific types. Thus, the returned list may have more + elements than type-list. + +.. function:: Misc.platform_independent_types(types) + + This command will accept a list of types that can contain platform + independent or platform specific types. A new list will be returned, where + each platform specific type in type-list will be substituted by one or more + platform independent types. Thus, the returned list may have more elements + than type-list. + +.. function:: Misc.get_drop_file_temp_directory() + + This command will return the temporary directory used by TkDND for storing + temporary files. When the package is loaded, this temporary directory will + be initialised to a proper directory according to the operating system. + + The default initial value can be changed by the following environment + variables: + + * ``TKDND_TEMP_DIR`` + * ``TEMP`` + * ``TMP`` + +.. function:: Misc.set_drop_file_temp_directory(directory) + + This command will change the temporary directory used by TkDND for storing + temporary files to the given directory. + +Constants +^^^^^^^^^ + +In order to declare the format that the data that will transferred during a +drag and drop operation, all drag and drop protocols use the notion of types. +Unfortunately, each protocol defines its own, usually platform specific, +types. TkDND, trying to maintain portability among different platforms, offers +some predefined types for basic kinds of data, like text and filenames. + +Platform Independent Types +>>>>>>>>>>>>>>>>>>>>>>>>>> + +Currently, the following predefined cross-platform values are available: + +.. data:: DND_ALL + + All supported types for the platform. + + .. warning:: ``DND_ALL`` can declare support for unexpected formats. For + example, it can declare special formats to receive text not declared by + ``DND_TEXT`` (such as RTF and HTML). While it appears to be able to receive + these types, support outside of the formats found as constants is not + guaranteed. + +.. data:: DND_TEXT + + This type can be used for transferring textual data. Internally, it is + translated to the following platform specific formats: + + +----------+------------------------------+ + | Platform | Type/s | + +==========+==============================+ + | Windows | .. data:: CF_UNICODETEXT | + | | CF_TEXT | + | | :noindex: | + +----------+------------------------------+ + | Unix | .. data:: TEXT_PLAIN_UNICODE | + | | TEXT_PLAIN | + | | :noindex: | + +----------+------------------------------+ + | Mac | .. data:: NSSTRINGPBOARDTYPE | + | | :noindex: | + +----------+------------------------------+ + +.. data:: DND_FILES + + This type can be used for transferring a list of filepaths/URLs. + Internally, it is translated to the following platform specific + formats: + + +----------+---------------------------------+ + | Platform | Type/s | + +==========+=================================+ + | Windows | .. data:: CF_HDROP | + | | :noindex: | + +----------+---------------------------------+ + | Unix | .. data:: URI_LIST | + | | :noindex: | + +----------+---------------------------------+ + | Mac | .. data:: NSFILENAMESPBOARDTYPE | + | | :noindex: | + +----------+---------------------------------+ + +Platform Specific Types +>>>>>>>>>>>>>>>>>>>>>>> + +Additionally to the platform independent types, TkDND supports the following +platform specific types (though it is often better to use an independent type +where possible): + +**Windows:** + + .. data:: CF_UNICODETEXT + + Text transfer encoded in Unicode. + + .. data:: CF_TEXT + + Text transfer with application dependent encoding. If the source has + specified an encoding it is used, else the system encoding is used for + the conversion. + + .. data:: CF_HDROP + + Filepath/URL transfer encoded in UTF-8. + +**Unix:** + + .. data:: TEXT_PLAIN_UNICODE + + Text transfer encoded in Unicode. + + .. data:: TEXT_PLAIN + + Text transfer with application dependent encoding. If the source has + specified an encoding it is used, else the system encoding is used for + the conversion. + + .. data:: URI_LIST + + Filepath/URL transfer encoded in ASCII. + +**Mac:** + + .. data:: NSSTRINGPBOARDTYPE + + Text transfer encoded using the system encoding. + + .. data:: NSFILENAMESPBOARDTYPE + + Filepath/URL transfer encoded using the system encoding. + +Finally, format types used for drop types can have wildcards, following the +same rules as "string match". For example, registering a drop target with the +type ``'*'`` (``DND_ALL``), will accept any drop, no matter what the drop +format is. + +Supported Actions +>>>>>>>>>>>>>>>>> + +These actions are returned by the relevant bindings to specify the action +that should take place. + +.. data:: ASK + + A dialog will be displayed to the user, in order to select an action. + +.. data:: COPY + + The data will be copied. + +.. data:: LINK + + The data will be linked. + +.. data:: MOVE + + The data will be moved. + +.. data:: PRIVATE + + A private action will be performed by the drop target. + +.. data:: REFUSE_DROP + + A drop cannot occur. + +Events +^^^^^^ + +Widgets registered as either drop targets or drag sources, will receive +certain events, during drag and drop operations. As a result, the widgets +are expected to have bindings for some of these events. Some events are +mandatory (in the sense that a drag or drop operation can be stopped if the +bindings do not exist), while others are not. + +In the following two sections all virtual events defined by the TkDND package +are presented. + +.. note:: It is a good practice to define bindings for all events, so that the + application will behave as expected during drag and drop operations. + +.. note:: While these event bindings are regular Tk events, they have a small + difference from plain Tk events, in that most of them are expected to return + a value. + +Drop Target Events +>>>>>>>>>>>>>>>>>> + +A widget registered as a drop target, can have bindings for the following +virtual events: + +``<>`` + + This event is triggered when the mouse enters the widget during a drop + action. The intention of this event is to change the visual state of the + widget, so as to notify the user whether the drop will be accepted or not. + The binding script is expected to return a single value that will define + the drop action. + + .. note:: This event is not mandatory, but if it is defined, it has to + return an action (otherwise the drop is refused). + +``<>`` + + This event is triggered when the mouse moves inside the widget during a + drop action (similar to the normal ```` event). The intention of + this event is to let widget decide if it will accept the drop and the + action of the drop, if a drop is going to happen at the specific mouse + coordinates. + + Thus, the script binding for such an event can get the mouse coordinates + and the pressed modifier buttons (such as ctrl, shift or alt), and is + expected to return the drop action. + + .. note:: This event is not mandatory, but if it is defined, it has to + return an action (otherwise the drop is refused). + +``<>`` + + This event is triggered when the mouse leaves the area covered by the + widget, without a drop happening. The binding of such an event is expected + to restore the visual state of the widget to normal (i.e. the visual state + the widget was in before the ``<>`` event was triggered). + + This event is not mandatory, and is not expected to return a value. + +``<>`` + + This event is triggered by a drop action, and it is expected to handle the + dropped data and reset the visual state of the widget. The binding script + is expected to return a value, which will be the action that has been + performed to the data. + + .. note:: This event is not mandatory, but if it is defined, it has to + return an action (otherwise the drop is refused). + +``*_DROP`` + + This event is a specialisation of the generic ``<>`` event, augmented + with a type. If such a binding exists and the drop type matches type, this + event binding will be executed, instead of the generic ``<>`` event + binding. + + For each content type constant (**except** ``DND_ALL``), there is a + ``_DROP`` version of it to provide the binding (e.g. the binding for a drop + of ``DND_FILES`` would be ``DND_FILES_DROP``) + + These events allow for easy specialisation of drop bindings, according to + the type of the dropped data. The binding script of such an event is + expected to return a value, which will be the action that has been + performed to the data. + + .. note:: This event is not mandatory, but if it is defined, it has to + return an action (otherwise the drop is refused). + +Drag Source Events +>>>>>>>>>>>>>>>>>> + +A widget registered as a drag source, is expected to have bindings for the +following virtual events: + +``<>`` + + This event is triggered when a drag action is about to start. This is a + mandatory event (whose absence will cancel the drag action), and is + responsible for providing a list containing three things: + + * A list of actions supported by the drag source + * A list format types supported by the drag source + * The data to be dropped + + A simple example of such a binding, is: :: + + drag_source.bind('<>', lambda event: \ + (COPY, DND_TEXT, 'Spam & eggs')) + +``<>`` + + This event is triggered when the drag action has finished (either when the + drop was successful or not). Its main purpose is to process the dropped + data according to the drop action returned by the drop target. + + This event is not mandatory, and is not expected to return a value. + +Event Data +>>>>>>>>>> + +.. data:: Event.action + + The current action of the drag/drop operation. + +.. data:: Event.code + + The code of the current type of the drag and drop operation. + +.. data:: Event.common_source_types + + The list of types from the drag source type list that are common to the + drop target type list. + +.. data:: Event.common_target_types + + The list of types from the drop target type list that are common to the + drag source type list. + +.. data:: Event.data + + The data that has been dropped. Under some platforms the data will be + available before the drop has occurred. The format of the data is the + current type of the drop operation. + + .. note:: This is always a list/tuple, even for text where there will only + be one item in the list/tuple. + +.. data:: Event.event_name + + The name of the current virtual event. One of ``<>``, + ``<>``, ``<>``, ``<>``, ``<>``, + ``<>`` and ``<>``. + +.. data:: Event.modifiers + + The list of modifier keyboard keys that are pressed. Modifier keys are some + special keys, like Shift, Control or Alt. Valid modifiers are "shift", + "ctrl" and "alt" (under all operating systems), and "mod1" to "mod5" under + Unix. + + .. note:: The usefulness of modifiers may differ across operating systems. + + For example, under Windows the drop target must examine + the modifiers, and decide upon the drop action that must be performed (by + selecting an action from the list of actions supported by the drag source). + + However, under Unix, the drag source is expected to decide on the drop + action, not the drop target. The drop target is expected to either accept + the action selected by the drag source (and return it back), or select + ``COPY``, ``DEFAULT``, or ``ASK``. So, under Unix examining the pressed + modifier keys fulfills only informative purposes. + +.. data:: Event.mouse_buttons + + The numbers of the mouse buttons pressed during a drag/drop operation. + + .. note:: Typically only a single mouse button is reported as pressed, even + if more than one mouse buttons are actually pressed. + + On Unix, however, the value will always be "1" unless the XKeyboard + extension is installed (in which case, the mouse buttons will be reported + correctly). + +.. data:: Event.source_actions + + The action list supported by the drag source. + +.. data:: Event.source_codes + + The codes of the list of types supported by the drag source. Codes are in + the same order as the list of types obtained through the ``source_types_t`` + attribute. + +.. data:: Event.source_types_L + + The list of types supported by the drag source (from the ``%L`` substitution). + +.. data:: Event.source_types_ST + + The list of types supported by the drag source (from the ``%ST`` substitution). + +.. data:: Event.source_types_t + + The list of types supported by the drag source (from the ``%t`` substitution). + +.. data:: Event.target_types + + The list of types supported by the drop target. + +.. data:: Event.type + + The current type of the drag and drop operation. + +.. data:: Event.widget + + The widget that the event is delivered to. + +.. data:: Event.x + + The mouse pointer x coordinate, relative to the drop target widget. + +.. data:: Event.x_root + + The mouse pointer x coordinate, relative to the root window. + +.. data:: Event.y + + The mouse pointer y coordinate, relative to the drop target widget. + +.. data:: Event.y_root + + The mouse pointer y coordinate, relative to the root window. + +Examples +^^^^^^^^ + +Specifying Drop Targets +>>>>>>>>>>>>>>>>>>>>>>> + +Creating drop targets is easy: we have to only register a widget as a drop +target with the list of format types it can accept, and add a few bindings. +For example, a widget that accepts textual drops can be as follows: :: + + def dropenter(event): + event.widget.configure(bg='yellow') + return COPY + drop_target = Label(text='Text Drop Target!', bg='white') + drop_target.pack() + drop_target.drop_target_register(DND_TEXT) + drop_target.bind('<>', dropenter) + drop_target.bind('<>', lambda event: COPY) + drop_target.bind('<>', lambda event: event.widget.configure(bg='white')) + drop_target.bind('<>', lambda event: \ + event.widget.configure(text=event.data, bg='white')) + +From the above bindings, none is obligatory. However, we usually want to +receive dropped data (thus the ``<>`` event must be handled) and we want +to give visual feedback to the users through the ``<>`` and +``<>`` events. Finally, ``<>`` is only necessary if +we want to only accept drops on specific areas of the widget, or we want to +change the drop action according to the pressed modifiers. + +Now, if we want to also add the ability to receive file drops, we could add: :: + + def filedrop(event): + print(event.data) + event.widget.configure(bg='white') + drop_target.drop_target_register(DND_FILES) + drop_target.bind(DND_FILES_DROP, filedrop) + +Note that we have added a "specialised" drop binding, for the event +``DND_FILES_DROP``: this means that when a text portion is dropped over the +widget, the ``<>`` event binding will be executed. But when a list of +files is dropped onto the widget, the ``DND_FILES_DROP`` event binding will be +executed. If we proceed and define a binding for the ``DND_TEXT_DROP`` event, +the binding of the "general" ``<>`` event will never be executed. + +Specifying Drag Sources +>>>>>>>>>>>>>>>>>>>>>>> + +In order to specify a drag source, we need to register a widget as a drag +source: :: + + text_drag_source.drag_source_register() + +The above command defines a drag source with an empty type list (and which +will be declared in the ``<>`` event binding) and arranges mouse +bindings such as a drag will be started with the left mouse button. Then, it +is absolutely necessary to define a binding for the ``<>``: this +event binding must return the list of actions, the list of format types and +the actual data to be dropped: :: + + text_drag_source.bind('<>', lambda event: \ + ((COPY, MOVE), DND_TEXT, 'Hello from Tk!')) + +Please note that all specified format types must be compatible to each other, +as they all characterise the same data. + +Specifying Drag Sources With Multiple Data Types +>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + +In the case the drag source wants to send a set of (incompatible) data types, +the result of the ``<>`` event binding must be slightly +different, as it must return two items (instead of three described +previously). + +The first element is again a list of allowable actions. However, the second +item is a list of "format type" and "data" pairs (each pair is **not** in +their own tuple/list): :: + + text_drag_source.bind('<>', lambda event: + ((COPY, MOVE), (DND_TEXT, 'Hello from Tk!', DND_FILES, '/tmp'))) + .. _tkinter-basic-mapping: Mapping Basic Tk into Tkinter diff --git a/Lib/tkinter/__init__.py b/Lib/tkinter/__init__.py index a3378d012fb41a..519a6ff3566f61 100644 --- a/Lib/tkinter/__init__.py +++ b/Lib/tkinter/__init__.py @@ -31,6 +31,7 @@ """ import enum +import os import sys import types @@ -48,6 +49,7 @@ WRITABLE = _tkinter.WRITABLE EXCEPTION = _tkinter.EXCEPTION +_DND_SUPPORT = {} _magic_re = re.compile(r'([\\{}])') _space_re = re.compile(r'([\s])', re.ASCII) @@ -765,6 +767,117 @@ def focus_lastfor(self): if name == 'none' or not name: return None return self._nametowidget(name) + def dnd_loaded(self): + """This function returns whether the TkDND library has been + successfully loaded for this Tk instance. + """ + return _DND_SUPPORT[self.tk] + + def _check_dnd(self): + """Checks if the interpreter has found the TkDND library and raises + a TclError if not (to protect methods which require TkDND). + """ + if not _DND_SUPPORT[self.tk]: + raise TclError("The TkDND library hasn't successfully loaded. If " + 'TkDND is already installed, try calling the ' + '"load_dnd" method with the install location.') + + def drop_target_register(self, types=()): + """This command will register the widget as a drop target (a widget + than can accept a drop action). An optional type-list can be provided, + which contains one or more types that the widget will accept during a + drop action. + + Note: This command can be executed multiple times on the same widget. + """ + self._check_dnd() + self.tk.call(('tkdnd::drop_target', 'register', self._w, types)) + + def drop_target_unregister(self): + """This command will stop the widget from being a drop target. Thus, it + will stop receiving events related to drop operations. + + Warning: This command will raise a TclError if the widget has not been + registered as a drop target. + """ + self._check_dnd() + self.tk.call(('tkdnd::drop_target', 'unregister', self._w)) + + def drag_source_register(self, types=(), mouse_buttons=1): + """This command will register the widget as a drag source. A drag + source is a widget than can start a drag action. When the widget is + registered as a drag source, an optional type-list can be provided. + This type list can contain one or more types that the widget will + provide during a drag action. However, this type list is + indicative/informative and the widget can initiate a drag action with a + type not in the list. + + Finally, mouse_buttons is one or more mouse buttons that can be used + for starting the drag action. It can have any of the values: + 1 - Left mouse button + 2 - Middle mouse button (wheel) + 3 - Right mouse button + + Multiple mouse buttons can be specified as a list/tuple of values. + + Note: This command can be executed multiple times on the same widget. + """ + self._check_dnd() + self.tk.call(('tkdnd::drag_source', 'register', + self._w, types, mouse_buttons)) + + def drag_source_unregister(self): + """This command will stop the widget from being a drag source. Thus, it + will stop receiving events related to drag operations. + + Warning: This command will raise a TclError if the widget has not been + registered as a drag source. + """ + self._check_dnd() + self.tk.call(('tkdnd::drag_source', 'unregister', self._w)) + + def platform_specific_types(self, types): + """This command will accept a list of types that can contain platform + independent or platform specific types. A new list will be returned, + where each platform independent type in type-list will be substituted + by one or more platform specific types. Thus, the returned list may + have more elements than type-list. + """ + self._check_dnd() + return self.tk.call(('tkdnd::platform_specific_types', types)) + + def platform_independent_types(self, types): + """This command will accept a list of types that can contain platform + independent or platform specific types. A new list will be returned, + where each platform specific type in type-list will be substituted by + one or more platform independent types. Thus, the returned list may + have more elements than type-list. + """ + self._check_dnd() + return self.tk.call(('tkdnd::platform_independent_types', types)) + + def get_drop_file_temp_directory(self): + """This command will return the temporary directory used by TkDND for + storing temporary files. When the package is loaded, this temporary + directory will be initialised to a proper directory according to the + operating system. + + The default initial value can be changed by the following environment + variables: + TKDND_TEMP_DIR + TEMP + TMP + """ + self._check_dnd() + return self.tk.call('tkdnd::GetDropFileTempDirectory') + + def set_drop_file_temp_directory(self, directory): + """This command will change the temporary directory used by TkDND for + storing temporary files to the given directory. + """ + self._check_dnd() + self.tk.call(('tkdnd::SetDropFileTempDirectory', directory)) + def tk_focusFollowsMouse(self): """The widget under mouse will get automatically focus. Can not be disabled easily.""" @@ -1332,7 +1445,8 @@ def _bind(self, what, sequence, func, add, needcleanup=1): elif func: funcid = self._register(func, self._substitute, needcleanup) - cmd = ('%sif {"[%s %s]" == "break"} break\n' + cmd = ('%sset dat [%s %s];if {"$dat" == "break"} {break} ' + 'elseif {"$dat" != ""} {set dndrtn $dat}\n' % (add and '+' or '', funcid, self._subst_format_str)) @@ -1532,9 +1646,12 @@ def _root(self): w = self while w.master: w = w.master return w - _subst_format = ('%#', '%b', '%f', '%h', '%k', - '%s', '%t', '%w', '%x', '%y', - '%A', '%E', '%K', '%N', '%W', '%T', '%X', '%Y', '%D') + _subst_format = ('%#', '{%a}', '{%b}', '{%c}', '%e', + '%f', '%h', '%k', '{%m}', '%s', + '{%t}', '%w', '%x', '%y', + '%A', '%C', '{%CST}', '{%CTT}', '{%D}', + '%E', '%K', '{%L}', '%N', '{%ST}', + '%T', '{%TT}', '%W', '%X', '%Y') _subst_format_str = " ".join(_subst_format) def _substitute(self, *args): @@ -1550,7 +1667,21 @@ def getint_event(s): except (ValueError, TclError): return s - nsign, b, f, h, k, s, t, w, x, y, A, E, K, N, W, T, X, Y, D = args + def getlist_event(s): + if s.startswith('{') and s.endswith('}'): + s = s[1:-1] + else: + return s, + s = s.translate((None, '\x00')) + if not s: + return '', + try: + return self.tk.splitlist(s) + except ValueError: + return s, + + nsign, a, b, c, ed, f, h, k, m, s, t, w, x, y, \ + A, C, CST, CTT, D, E, K, L, N, ST, T, TT, W, X, Y = args # Missing: (a, c, d, m, o, v, B, R) e = Event() # serial field: valid for all events @@ -1566,26 +1697,43 @@ def getint_event(s): # keysym as decimal: KeyPress and KeyRelease events only # x_root, y_root fields: ButtonPress, ButtonRelease, KeyPress, # KeyRelease, and Motion events - e.serial = getint(nsign) - e.num = getint_event(b) + try: + e.serial = getint(nsign) + except (ValueError, TclError): + e.serial = None + e.source_actions = getlist_event(a) + e.mouse_buttons = tuple(map(getint_event, getlist_event(b))) + e.num = e.mouse_buttons[0] + e.source_codes = tuple(map(getint_event, getlist_event(c))) + e.event_name = ed try: e.focus = getboolean(f) except TclError: pass e.height = getint_event(h) e.keycode = getint_event(k) + e.modifiers = getlist_event(m) e.state = getint_event(s) - e.time = getint_event(t) + e.source_types_t = getlist_event(t) + e.time = getint_event(e.source_types_t[0]) e.width = getint_event(w) e.x = getint_event(x) e.y = getint_event(y) e.char = A + e.action = e.char + e.code = getint_event(C) + e.common_source_types = getlist_event(CST) + e.common_target_types = getlist_event(CTT) + e.data = getlist_event(D) try: e.send_event = getboolean(E) except TclError: pass e.keysym = K + e.source_types_L = getlist_event(L) e.keysym_num = getint_event(N) + e.source_types_ST = getlist_event(ST) try: e.type = EventType(T) except ValueError: e.type = T + e.target_types = getlist_event(TT) try: e.widget = self._nametowidget(W) except KeyError: @@ -2261,6 +2409,7 @@ def __init__(self, screenName=None, baseName=None, className='Tk', baseName = baseName + ext interactive = False self.tk = _tkinter.create(screenName, baseName, className, interactive, wantobjects, useTk, sync, use) + _DND_SUPPORT[self.tk] = False if useTk: self._loadtk() if not sys.flags.ignore_environment: @@ -2298,6 +2447,34 @@ def _loadtk(self): _default_root = self self.protocol("WM_DELETE_WINDOW", self.destroy) + self.load_dnd() + + def load_dnd(self, dnd_path=None): + """This command will load the TkDND library and is called when Tk class + is instantiated. However, if the library is installed in a different + directory, this method will need to be called manually to load the + library. If the TkDND library has been found, True wil be returned, + otherwise False will be returned. + + Note: If the TkDND library has already been successfully loaded, True + will be returned and no action taken. + """ + if _DND_SUPPORT.get(self.tk, False): + return True + if not self._tkloaded: + return False + if dnd_path is not None: + # An absolute path must always be used + self.tk.call(('lappend', 'auto_path', os.path.abspath(dnd_path))) + try: + self.tk.call(('package', 'require', 'tkdnd')) + except TclError: + sup = False + else: + sup = True + _DND_SUPPORT[self.tk] = sup + return sup + def destroy(self): """Destroy this and all descendants widgets. This will end the application of this Tcl interpreter.""" diff --git a/Lib/tkinter/constants.py b/Lib/tkinter/constants.py index 63eee33d24d6c7..2be25f0df96b4e 100644 --- a/Lib/tkinter/constants.py +++ b/Lib/tkinter/constants.py @@ -108,3 +108,34 @@ SCROLL='scroll' UNITS='units' PAGES='pages' + +# DND constants +DND_ALL = '*' +DND_FILES = 'DND_Files' +DND_TEXT = 'DND_Text' +CF_UNICODETEXT = 'CF_UNICODETEXT' +CF_TEXT = 'CF_TEXT' +CF_HDROP = 'CF_HDROP' +TEXT_PLAIN_UNICODE = 'text/plain;charset=UTF-8' +TEXT_PLAIN = 'text/plain' +URI_LIST = 'text/uri-list' +NSSTRINGPBOARDTYPE = 'NSStringPboardType' +NSFILENAMESPBOARDTYPE = 'NSFilenamesPboardType' + +DND_FILES_DROP = '<>' +DND_TEXT_DROP = '<>' +CF_UNICODETEXT_DROP = '<>' +CF_TEXT_DROP = '<>' +CF_HDROP_DROP = '<>' +TEXT_PLAIN_UNICODE_DROP = '<>' +TEXT_PLAIN_DROP = '<>' +URI_LIST_DROP = '<>' +NSSTRINGPBOARDTYPE_DROP = '<>' +NSFILENAMESPBOARDTYPE_DROP = '<>' + +ASK = 'ask' +COPY = 'copy' +LINK = 'link' +MOVE = 'move' +PRIVATE = 'private' +REFUSE_DROP = 'refuse_drop' diff --git a/Lib/tkinter/dnd.py b/Lib/tkinter/dnd.py index 3120ff342f8c0e..867dfd93bf9ab8 100644 --- a/Lib/tkinter/dnd.py +++ b/Lib/tkinter/dnd.py @@ -100,6 +100,12 @@ """ import tkinter +import warnings + +warnings.warn("the tkinter.dnd module is deprecated in favour of the TkDND " + "bindings found in the main tkinter module (see the " + "documentation)", + DeprecationWarning, stacklevel=2) __all__ = ["dnd_start", "DndHandler"] diff --git a/PCbuild/get_externals.bat b/PCbuild/get_externals.bat index 4171fd740c7083..77e4767f256325 100644 --- a/PCbuild/get_externals.bat +++ b/PCbuild/get_externals.bat @@ -58,6 +58,7 @@ set libraries=%libraries% sqlite-3.31.1.0 if NOT "%IncludeTkinterSrc%"=="false" set libraries=%libraries% tcl-core-8.6.9.0 if NOT "%IncludeTkinterSrc%"=="false" set libraries=%libraries% tk-8.6.9.0 if NOT "%IncludeTkinterSrc%"=="false" set libraries=%libraries% tix-8.4.3.6 +if NOT "%IncludeTkinterSrc%"=="false" set libraries=%libraries% tkdnd-2.9.2 set libraries=%libraries% xz-5.2.2 set libraries=%libraries% zlib-1.2.11 @@ -79,6 +80,7 @@ set binaries= if NOT "%IncludeLibffi%"=="false" set binaries=%binaries% libffi if NOT "%IncludeSSL%"=="false" set binaries=%binaries% openssl-bin-1.1.1g if NOT "%IncludeTkinter%"=="false" set binaries=%binaries% tcltk-8.6.9.0 +if NOT "%IncludeTkinter%"=="false" set binaries=%binaries% tkdnd-bin-2.9.2 if NOT "%IncludeSSLSrc%"=="false" set binaries=%binaries% nasm-2.11.06 for %%b in (%binaries%) do ( diff --git a/PCbuild/tcltk.props b/PCbuild/tcltk.props index 7fcd3e1c618c46..be8c7d49cb3bbb 100644 --- a/PCbuild/tcltk.props +++ b/PCbuild/tcltk.props @@ -14,10 +14,15 @@ 4 3 6 + 2 + 9 + 2 $(ExternalsDir)tcl-core-$(TclMajorVersion).$(TclMinorVersion).$(TclPatchLevel).$(TclRevision)\ $(ExternalsDir)tk-$(TkMajorVersion).$(TkMinorVersion).$(TkPatchLevel).$(TkRevision)\ $(ExternalsDir)tix-$(TixMajorVersion).$(TixMinorVersion).$(TixPatchLevel).$(TixRevision)\ $(ExternalsDir)tcltk-$(TclMajorVersion).$(TclMinorVersion).$(TclPatchLevel).$(TclRevision)\$(ArchName)\ + $(ExternalsDir)tkdnd-bin-$(TkdndMajorVersion).$(TkdndMinorVersion).$(TkdndPatchLevel)\$(ArchName)\ + tkdnd-$(TkdndMajorVersion).$(TkdndMinorVersion).$(TkdndPatchLevel) tcl$(TclMajorVersion)$(TclMinorVersion)t$(TclDebugExt).dll tcl$(TclMajorVersion)$(TclMinorVersion)t$(TclDebugExt).lib diff --git a/Tools/msi/tcltk/tcltk.wixproj b/Tools/msi/tcltk/tcltk.wixproj index 218f3d15ec88fc..249afbce8fa3b6 100644 --- a/Tools/msi/tcltk/tcltk.wixproj +++ b/Tools/msi/tcltk/tcltk.wixproj @@ -28,6 +28,14 @@ tcltk_lib + + $(tkdndDir) + $(tkdndDir) + $(tkdndDir) + tcl\$(tkdndDest)\ + tcltk_lib + + $(PySourcePath)