Skip to content

dialog

dialog Documentation

A module with MTP relevant dialogs for tkinter

Author: Heribert Füchtenhans

Version: 2025.6.25

Requirements
  • Python modules
    • tkinter

The module contains the following classes:

  • 'AskDirectory' - Class to select a directory on an MTP device

Examples:

>>> from mtp import dialog
>>> root = tkinter.Tk()
>>> root.title("mtp_dialogs")
>>> adir = dialog.AskDirectory(root, "Test ask_directory", ("Alls well", "Don't do it"))

AskDirectory

Bases: Dialog

Select a wpd device and directory.

Attributes:

Name Type Description
answer str

The MTP path to the selected directory

wpd_device PortableDevice | None

The PortableDevice that was selected

Raises:

Type Description
IOError

exceptions if something went wrong

Source code in mtp/dialog.py
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
class AskDirectory(tkinter.simpledialog.Dialog):  # pylint: disable=too-many-instance-attributes
    """Select a wpd device and directory.

    Attributes:
        answer: The MTP path to the selected directory
        wpd_device: The PortableDevice that was selected

    Exceptions:
        IOError: exceptions if something went wrong
    """

    def __init__(
        self,
        parent: tkinter.Tk,
        title: str,
        buttons: tuple[str, str] = ("OK", "Cancel"),
    ) -> None:
        """Initializes the class and shows the directory dialog.

        Args:
            parent: The tkinter parent
            title: The windows title
            buttons: A tuple with the text for the OK and cancel button to chnage them
                     for other languages.
        """
        self._parent: tkinter.Tk = parent
        self._dialog_title: str = title
        self._tree: ttk.Treeview
        self._smartphone_icon:tkinter.PhotoImage = tkinter.PhotoImage(data=SMARTPHONE_ICON)
        self._buttons: tuple[str, str] = buttons
        self._tree_entries: dict[str, _TreeEntry] = {}
        # external variables
        self.answer: str = ""
        self.wpd_device: access.PortableDevice | None = None
        tkinter.simpledialog.Dialog.__init__(self, parent, title=title)
        self.update_idletasks()

    @override
    def buttonbox(self) -> None:
        """Create own buttons"""
        box = ttk.Frame(self)
        box.pack(side=tkinter.TOP, fill=tkinter.BOTH)
        but = ttk.Button(box, text=self._buttons[1], command=self.cancel)
        but.pack(side=tkinter.RIGHT, padx=5, pady=5)
        but = ttk.Button(box, text=self._buttons[0], command=self._on_ok, default=tkinter.ACTIVE)
        but.pack(side=tkinter.RIGHT, padx=5, pady=5)
        but.focus_set()
        _ = but.bind("<Return>", self.ok)

    @override
    def body(self, master: tkinter.Frame) -> None:
        """Create body"""
        box = ttk.Frame(self)
        box.pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=True, padx=5, pady=5)
        self._tree = ttk.Treeview(box, height=20, show="tree")
        _ = self._tree.column("#0", width=500)
        _ = self._tree.bind("<<TreeviewOpen>>", self._on_treeselect)
        # adding data, get devices
        for dev in access.get_portable_devices():
            if devicename := dev.devicename:
                treeid = self._tree.insert(
                    "",
                    tkinter.END,
                    text=devicename,
                    open=False,
                    image=self._smartphone_icon,
                )
                self._tree_entries[treeid] = _TreeEntry(dev, None, [], False)
                _ = self.config(cursor="watch")
                self.update_idletasks()
                self._process_directory(treeid)
                _ = self.config(cursor="")
        # place the Treeview widget on the root window
        self._tree.pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=True)

    def _process_directory(self, insert_after_id: str) -> None:
        """Insert directory listing until depth is 0"""
        treeentry = self._tree_entries[insert_after_id]
        if treeentry.content is not None:
            cont = list(treeentry.content.get_children())
        else:
            cont = list(treeentry.dev.get_content())
        if len(cont) == 0:  # no children
            return
        for child in cont:
            if child.content_type not in (
                access.WPD_CONTENT_TYPE_STORAGE,
                access.WPD_CONTENT_TYPE_DIRECTORY,
            ):
                continue
            with contextlib.suppress(tkinter.TclError):
                treeid = self._tree.insert(
                    insert_after_id,
                    tkinter.END,
                    text=child.name,
                    open=False,
                )
                self._tree_entries[treeid] = _TreeEntry(treeentry.dev, child, [], False)
                treeentry.child_treeids.append(treeid)
        self._tree_entries[insert_after_id].content_loaded = True

    def _on_treeselect(self, _: tkinter.Event) -> None:
        """Will be called on very selection"""
        treeid = self._tree.focus()
        status = self._tree.item(treeid, "open")
        if not status:
            __ = self.config(cursor="watch")
            self.update_idletasks()
            for c_id in self._tree_entries[treeid].child_treeids:
                if not self._tree_entries[c_id].content_loaded:
                    self._process_directory(c_id)
            __ = self.config(cursor="")
            self._tree.item(treeid, open=True)
        else:
            self._tree.item(treeid, open=False)

    def _on_ok(self) -> None:
        """OK Button"""
        self.withdraw()
        self.update_idletasks()
        treeid = self._tree.focus()
        if treeid == "":
            self.cancel()
        else:
            cont = self._tree_entries[treeid].content
            if cont is None:
                self.cancel()
                return
            self.answer = cont.full_filename
            self.wpd_device = self._tree_entries[treeid].dev
            try:
                self.apply()
            finally:
                self.cancel()

__init__(parent, title, buttons=('OK', 'Cancel'))

Initializes the class and shows the directory dialog.

Parameters:

Name Type Description Default
parent Tk

The tkinter parent

required
title str

The windows title

required
buttons tuple[str, str]

A tuple with the text for the OK and cancel button to chnage them for other languages.

('OK', 'Cancel')
Source code in mtp/dialog.py
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
def __init__(
    self,
    parent: tkinter.Tk,
    title: str,
    buttons: tuple[str, str] = ("OK", "Cancel"),
) -> None:
    """Initializes the class and shows the directory dialog.

    Args:
        parent: The tkinter parent
        title: The windows title
        buttons: A tuple with the text for the OK and cancel button to chnage them
                 for other languages.
    """
    self._parent: tkinter.Tk = parent
    self._dialog_title: str = title
    self._tree: ttk.Treeview
    self._smartphone_icon:tkinter.PhotoImage = tkinter.PhotoImage(data=SMARTPHONE_ICON)
    self._buttons: tuple[str, str] = buttons
    self._tree_entries: dict[str, _TreeEntry] = {}
    # external variables
    self.answer: str = ""
    self.wpd_device: access.PortableDevice | None = None
    tkinter.simpledialog.Dialog.__init__(self, parent, title=title)
    self.update_idletasks()

body(master)

Create body

Source code in mtp/dialog.py
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
@override
def body(self, master: tkinter.Frame) -> None:
    """Create body"""
    box = ttk.Frame(self)
    box.pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=True, padx=5, pady=5)
    self._tree = ttk.Treeview(box, height=20, show="tree")
    _ = self._tree.column("#0", width=500)
    _ = self._tree.bind("<<TreeviewOpen>>", self._on_treeselect)
    # adding data, get devices
    for dev in access.get_portable_devices():
        if devicename := dev.devicename:
            treeid = self._tree.insert(
                "",
                tkinter.END,
                text=devicename,
                open=False,
                image=self._smartphone_icon,
            )
            self._tree_entries[treeid] = _TreeEntry(dev, None, [], False)
            _ = self.config(cursor="watch")
            self.update_idletasks()
            self._process_directory(treeid)
            _ = self.config(cursor="")
    # place the Treeview widget on the root window
    self._tree.pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=True)

buttonbox()

Create own buttons

Source code in mtp/dialog.py
107
108
109
110
111
112
113
114
115
116
117
@override
def buttonbox(self) -> None:
    """Create own buttons"""
    box = ttk.Frame(self)
    box.pack(side=tkinter.TOP, fill=tkinter.BOTH)
    but = ttk.Button(box, text=self._buttons[1], command=self.cancel)
    but.pack(side=tkinter.RIGHT, padx=5, pady=5)
    but = ttk.Button(box, text=self._buttons[0], command=self._on_ok, default=tkinter.ACTIVE)
    but.pack(side=tkinter.RIGHT, padx=5, pady=5)
    but.focus_set()
    _ = but.bind("<Return>", self.ok)