Source code for mytk.dnd

"""Drag-and-drop support for myTk — accept files dropped from the OS.

Tk has no native OS-level drag-and-drop. This wraps the **tkdnd** Tcl extension
(via the ``tkinterdnd2`` package) and retrofits it onto myTk's existing root
window, so any widget can opt in with :meth:`Base.accept_dropped_files` without
the app having to create a special drag-aware root.

tkdnd ships as a compiled library built against a specific Tcl major version, so
the right ``tkinterdnd2`` release is chosen from the running Tcl: the 0.4.x line
for Tcl 8.x, the latest for Tcl 9.x. If the extension still cannot be loaded
(for instance an incompatible build is already installed), drag-and-drop is
simply reported as unavailable and the calling code carries on without it.
"""

from .modulesmanager import ModulesManager

# Roots that already have tkdnd loaded, so we only retrofit each one once.
_dnd_ready_roots = set()


def _tkinterdnd2_spec(root):
    """pip spec for a ``tkinterdnd2`` whose tkdnd matches the running Tcl."""
    patchlevel = root.tk.call("info", "patchlevel")  # e.g. "8.6.17" / "9.0.1"
    major = int(patchlevel.split(".")[0])
    # 0.5.0+ bundles a Tcl-9 build; the 0.4.x line bundles a Tcl-8 build.
    return "tkinterdnd2" if major >= 9 else "tkinterdnd2==0.4.2"


[docs] def ensure_tkdnd(root, ask_for_confirmation=True): """Load tkdnd onto ``root``, installing a compatible tkinterdnd2 if needed. Returns the imported ``tkinterdnd2`` module when drag-and-drop is available on this root, or ``None`` if it could not be enabled (missing or incompatible extension). Importing the package also patches Tk's widget classes with the ``drop_target_register`` / ``dnd_bind`` methods. """ spec = _tkinterdnd2_spec(root) ModulesManager.install_and_import_modules_if_absent( {spec: "tkinterdnd2"}, ask_for_confirmation=ask_for_confirmation ) tkdnd = ModulesManager.imported.get(spec) if tkdnd is None: return None if root not in _dnd_ready_roots: try: tkdnd.TkinterDnD._require(root) except Exception: return None # e.g. tkdnd built for a different Tcl major version _dnd_ready_roots.add(root) return tkdnd
[docs] def dropped_paths(root, event_data): """Parse a tkdnd ``<<Drop>>`` payload into a list of filesystem paths. tkdnd hands over a Tcl list (paths with spaces are brace-wrapped); ``splitlist`` turns it back into individual paths. """ return list(root.tk.splitlist(event_data))