Skip to content

Document

Usage

A common requirement for apps is to view or edit a particular type of file. In Toga, you define a toga.Document class to represent each type of content that your app is able to manipulate. This Document class is then registered with your app when the App instance is created.

The toga.Document class describes how your document can be read, displayed, and saved. It also tracks whether the document has been modified. In this example, the code declares an "Example Document" document type, which will create files with the extensions .mydoc and .mydocument; because it is listed first, the .mydoc extension will be the default for documents of this type. The main window for this document type contains a MultilineTextInput. Whenever the content of that widget changes, the document is marked as modified:

import toga

class ExampleDocument(toga.Document):
    description = "Example Document"
    extensions = [`"mydoc", "mydocument"]

    def create(self):
        # Create the main window for the document. The window has a single widget;
        # when that widget changes, the document is modified.
        self.main_window = toga.DocumentMainWindow(
            doc=self,
            content=toga.MultilineTextInput(on_change=self.touch),
        )

    def read(self):
        # Read the content of the file represented by the document, and populate the
        # widgets in the main window with that content.
        with self.path.open() as f:
            self.main_window.content.value = f.read()

    def write(self):
        # Save the content currently displayed by the main window.
        with self.path.open("w") as f:
            f.write(self.main_window.content.value)

The document window uses the modification status to determine whether the window is allowed to close. If a document is modified, the user will be asked if they want to save changes to the document.

Registering document types

A document type is used by registering it with an app instance. The constructor for toga.App allows you to declare the collection of document types that your app supports. The first declared document type is treated as the default document type for your app; this is the type that will be connected to the keyboard shortcut of the NEW command.

After startup() returns, any filenames which were passed to the app by the operating system will be opened using the registered document types. If after this the app still has no windows, then:

  • On Windows and GTK, an untitled document of the default type will be opened.
  • On macOS, an Open dialog will be shown.

In the following example, the app will be able to manage documents of type ExampleDocument or OtherDocument, with ExampleDocument being the default content type. The app is configured to not have a single "main" window, so the life cycle of the app is not tied to a specific window.

import toga

class ExampleApp(toga.App):
    def startup(self):
        # The app does not have a single main window
        self.main_window = None

app = ExampleApp(
    "Document App",
    "com.example.documentapp",
    document_types=[ExampleDocument, OtherDocument]
)

app.main_loop()

By declaring these document types, the app will automatically have file management commands (New, Open, Save, etc) added.

Reference

Bases: ABC

Source code in core/src/toga/documents.py
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 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
class Document(ABC):
    #: A short description of the type of document (e.g., "Text document"). This is a
    #: class variable that subclasses should define.
    description: str

    #: A list of extensions that documents of this type might use, without leading dots
    #: (e.g., `["doc", "txt"]`). The list must have at least one extension; the first
    #: is the default extension for documents of this type. This is a class variable
    #: that subclasses should define.
    extensions: list[str]

    def __init__(self, app: App):
        """Create a new Document. Do not call this constructor directly - use
        [`DocumentSet.new`][toga.documents.DocumentSet.new],
        [`DocumentSet.open`][toga.documents.DocumentSet.open] or
        [`DocumentSet.request_open`][toga.documents.DocumentSet.request_open] instead.

        :param app: The application the document is associated with.
        """
        self._path: Path | None = None
        self._app = app
        self._main_window: Window | None = None
        self.modified = False

        # Create the visual representation of the document.
        self.create()

        # Add the document to the list of managed documents.
        self.app.documents._add(self)

    ######################################################################
    # Document properties
    ######################################################################
    @property
    def path(self) -> Path:
        """The path where the document is stored (read-only)."""
        return self._path

    @property
    def app(self) -> App:
        """The app that this document is associated with (read-only)."""
        return self._app

    @property
    def main_window(self) -> Window | None:
        """The main window for the document."""
        return self._main_window

    @main_window.setter
    def main_window(self, window: Window) -> None:
        self._main_window = window

    @property
    def title(self) -> str:
        """The title of the document.

        This will be used as the default title of a [`toga.DocumentWindow`][] that
        contains the document.
        """
        return f"{self.description}: {self.path.stem if self.path else 'Untitled'}"

    @property
    def modified(self) -> bool:
        """Has the document been modified?"""
        return self._modified

    @modified.setter
    def modified(self, value: bool):
        self._modified = bool(value)

    ######################################################################
    # Document operations
    ######################################################################

    def focus(self):
        """Give the document focus in the app."""
        self.app.current_window = self.main_window

    def hide(self) -> None:
        """Hide the visual representation for this document."""
        self.main_window.hide()

    def open(self, path: str | Path):
        """Open a file as a document.

        :param path: The file to open.
        """
        self._path = Path(path).absolute()
        if self._path.exists():
            self.read()
        else:
            self._path = None
            raise FileNotFoundError()

        # Set the title of the document window to match the path
        self._main_window.title = self._main_window._default_title
        # Document is initially unmodified
        self.modified = False

    def save(self, path: str | Path | None = None):
        """Save the document as a file.

        If a path is provided, the path for the document will be updated. Otherwise, the
        existing path will be used.

        If the [`Document.write()`][toga.Document.write] method has not been
        implemented, this method is a no-op.

        :param path: If provided, the new file name for the document.
        """
        if self._writable():
            if path:
                self._path = Path(path).absolute()
                # Re-set the title of the document with the new path
                self._main_window.title = self._main_window._default_title
            self.write()
            # Clear the modification flag.
            self.modified = False

    # A document is writable if its class overrides the `write` method.
    def _writable(self):
        return type(self).write is not Document.write

    def show(self) -> None:
        """Show the visual representation for this document."""
        self.main_window.show()

    def touch(self, *args, **kwargs):
        """Mark the document as modified.

        This method accepts `*args` and `**kwargs` so that it can be used as an
        `on_change` handler; these arguments are not used.
        """
        self.modified = True

    ######################################################################
    # Abstract interface
    ######################################################################

    @abstractmethod
    def create(self) -> None:
        """Create the window (or windows) for the document.

        This method must, at a minimum, assign the
        [`main_window`][toga.Document.main_window] property. It
        may also create additional windows or UI elements if desired.
        """

    @abstractmethod
    def read(self) -> None:
        """Load a representation of the document into memory from
        [`Document.path`][toga.Document.path], and populate the document window.
        """

    def write(self) -> None:  # noqa: B027 (it's intentionally blank)
        """Persist a representation of the current state of the document.

        This method is a no-op by default, to allow for read-only document types.
        """

app property

The app that this document is associated with (read-only).

main_window property writable

The main window for the document.

modified property writable

Has the document been modified?

path property

The path where the document is stored (read-only).

title property

The title of the document.

This will be used as the default title of a toga.DocumentWindow that contains the document.

__init__(app)

Create a new Document. Do not call this constructor directly - use DocumentSet.new, DocumentSet.open or DocumentSet.request_open instead.

:param app: The application the document is associated with.

Source code in core/src/toga/documents.py
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
def __init__(self, app: App):
    """Create a new Document. Do not call this constructor directly - use
    [`DocumentSet.new`][toga.documents.DocumentSet.new],
    [`DocumentSet.open`][toga.documents.DocumentSet.open] or
    [`DocumentSet.request_open`][toga.documents.DocumentSet.request_open] instead.

    :param app: The application the document is associated with.
    """
    self._path: Path | None = None
    self._app = app
    self._main_window: Window | None = None
    self.modified = False

    # Create the visual representation of the document.
    self.create()

    # Add the document to the list of managed documents.
    self.app.documents._add(self)

create() abstractmethod

Create the window (or windows) for the document.

This method must, at a minimum, assign the main_window property. It may also create additional windows or UI elements if desired.

Source code in core/src/toga/documents.py
156
157
158
159
160
161
162
163
@abstractmethod
def create(self) -> None:
    """Create the window (or windows) for the document.

    This method must, at a minimum, assign the
    [`main_window`][toga.Document.main_window] property. It
    may also create additional windows or UI elements if desired.
    """

focus()

Give the document focus in the app.

Source code in core/src/toga/documents.py
91
92
93
def focus(self):
    """Give the document focus in the app."""
    self.app.current_window = self.main_window

hide()

Hide the visual representation for this document.

Source code in core/src/toga/documents.py
95
96
97
def hide(self) -> None:
    """Hide the visual representation for this document."""
    self.main_window.hide()

open(path)

Open a file as a document.

:param path: The file to open.

Source code in core/src/toga/documents.py
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
def open(self, path: str | Path):
    """Open a file as a document.

    :param path: The file to open.
    """
    self._path = Path(path).absolute()
    if self._path.exists():
        self.read()
    else:
        self._path = None
        raise FileNotFoundError()

    # Set the title of the document window to match the path
    self._main_window.title = self._main_window._default_title
    # Document is initially unmodified
    self.modified = False

read() abstractmethod

Load a representation of the document into memory from Document.path, and populate the document window.

Source code in core/src/toga/documents.py
165
166
167
168
169
@abstractmethod
def read(self) -> None:
    """Load a representation of the document into memory from
    [`Document.path`][toga.Document.path], and populate the document window.
    """

save(path=None)

Save the document as a file.

If a path is provided, the path for the document will be updated. Otherwise, the existing path will be used.

If the Document.write() method has not been implemented, this method is a no-op.

:param path: If provided, the new file name for the document.

Source code in core/src/toga/documents.py
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
def save(self, path: str | Path | None = None):
    """Save the document as a file.

    If a path is provided, the path for the document will be updated. Otherwise, the
    existing path will be used.

    If the [`Document.write()`][toga.Document.write] method has not been
    implemented, this method is a no-op.

    :param path: If provided, the new file name for the document.
    """
    if self._writable():
        if path:
            self._path = Path(path).absolute()
            # Re-set the title of the document with the new path
            self._main_window.title = self._main_window._default_title
        self.write()
        # Clear the modification flag.
        self.modified = False

show()

Show the visual representation for this document.

Source code in core/src/toga/documents.py
140
141
142
def show(self) -> None:
    """Show the visual representation for this document."""
    self.main_window.show()

touch(*args, **kwargs)

Mark the document as modified.

This method accepts *args and **kwargs so that it can be used as an on_change handler; these arguments are not used.

Source code in core/src/toga/documents.py
144
145
146
147
148
149
150
def touch(self, *args, **kwargs):
    """Mark the document as modified.

    This method accepts `*args` and `**kwargs` so that it can be used as an
    `on_change` handler; these arguments are not used.
    """
    self.modified = True

write()

Persist a representation of the current state of the document.

This method is a no-op by default, to allow for read-only document types.

Source code in core/src/toga/documents.py
171
172
173
174
175
def write(self) -> None:  # noqa: B027 (it's intentionally blank)
    """Persist a representation of the current state of the document.

    This method is a no-op by default, to allow for read-only document types.
    """

Bases: Sequence[Document], Mapping[Path, Document]

Source code in core/src/toga/documents.py
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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
class DocumentSet(Sequence[Document], Mapping[Path, Document]):
    def __init__(self, app: App, types: list[type[Document]]):
        """A collection of documents managed by an app.

        A document is automatically added to the app when it is created, and removed
        when it is closed. The document collection will be stored in the order that
        documents were created.

        :param app: The app that this instance is bound to.
        :param types: The document types managed by this app.
        """
        self.app = app
        for doc_type in types:
            if not hasattr(doc_type, "description"):
                raise ValueError(
                    f"Document type {doc_type.__name__!r} "
                    "doesn't define a 'descriptions' attribute"
                )
            if not hasattr(doc_type, "extensions"):
                raise ValueError(
                    f"Document type {doc_type.__name__!r} "
                    "doesn't define an 'extensions' attribute"
                )
            if len(doc_type.extensions) == 0:
                raise ValueError(
                    f"Document type {doc_type.__name__!r} "
                    "doesn't define at least one extension"
                )

        self._types = types

        self.elements: list[Document] = []

    @property
    def types(self) -> list[type[Document]]:
        """The list of document types the app can manage.

        The first document type in the list is the app's default document type.
        """
        return self._types

    def __iter__(self) -> Iterator[Document]:
        return iter(self.elements)

    def __contains__(self, value: object) -> bool:
        return value in self.elements

    def __len__(self) -> int:
        return len(self.elements)

    def __getitem__(self, path_or_index):
        # Look up by index
        if isinstance(path_or_index, int):
            return self.elements[path_or_index]

        # Look up by path
        path = Path(path_or_index).resolve()
        for item in self.elements:
            if item.path == path:
                return item

        # No match found
        raise KeyError(path_or_index)

    def _add(self, document: Path):
        if document in self:
            raise ValueError("Document is already being managed.")

        self.elements.append(document)

    def _remove(self, document: Path):
        if document not in self:
            raise ValueError("Document is not being managed.")

        self.elements.remove(document)

    def new(self, document_type: type[Document]) -> Document:
        """Create a new document of the given type, and show the document window.

        :param document_type: The document type that has been requested.
        :returns: The newly created document.
        """
        document = document_type(app=self.app)
        document.show()
        return document

    async def request_open(self) -> Document:
        """Present a dialog asking the user for a document to open, and pass the
        selected path to [`DocumentSet.open()`][toga.documents.DocumentSet.open].

        :returns: The document that was opened.
        :raises ValueError: If the path describes a file that is of a type that doesn't
            match a registered document type.
        """
        # A safety catch: if app modal dialogs aren't actually modal (eg, macOS) prevent
        # a second open dialog from being opened when one is already active. Attach the
        # dialog instance as a private attribute; delete as soon as the future is
        # complete.
        if hasattr(self, "_open_dialog"):
            return

        # CLOSE_ON_LAST_WINDOW is a proxy for the GTK/Windows behavior of loading
        # content into the existing window. This is actually implemented by creating a
        # new window and disposing of the old one; mark the current window for cleanup
        current_window = self.app.current_window
        if self.app._impl.CLOSE_ON_LAST_WINDOW:
            if hasattr(self.app.current_window, "_commit"):
                if await self.app.current_window._commit():
                    current_window._replace = True
                else:
                    # The changes in the current document window couldn't be committed
                    # (e.g., a save was requested, but then cancelled), so we can't
                    # proceed with opening a new document.
                    return

        self._open_dialog = dialogs.OpenFileDialog(
            self.app.formal_name,
            file_types=(
                list(itertools.chain(*(doc_type.extensions for doc_type in self.types)))
                if self.types
                else None
            ),
        )
        path = await self.app.dialog(self._open_dialog)
        del self._open_dialog

        try:
            if path:
                return self.open(path)
        finally:
            # Remove the replacement marker
            if hasattr(current_window, "_replace"):
                del current_window._replace

    def open(self, path: Path | str) -> Document:
        """Open a document in the app, and show the document window.

        If the provided path is already an open document, the existing representation
        for the document will be given focus.

        :param path: The path to the document to be opened.
        :returns: The document that was opened.
        :raises ValueError: If the path describes a file that is of a type that doesn't
            match a registered document type.
        """
        path = Path(path).resolve()

        try:
            document = self.app.documents[path]
            document.focus()
            return document
        except KeyError:
            # No existing representation for the document.
            try:
                DocType = {
                    extension: doc_type
                    for doc_type in self.types
                    for extension in doc_type.extensions
                }[path.suffix[1:]]
            except KeyError as exc:
                raise ValueError(
                    f"Don't know how to open documents with extension {path.suffix}"
                ) from exc
            else:
                prev_window = self.app.current_window
                document = DocType(app=self.app)
                try:
                    document.open(path)

                    # If the previous window is marked for replacement, close it; but
                    # put the new document window in the same position as the previous
                    # one.
                    if getattr(prev_window, "_replace", False):
                        document.main_window.position = prev_window.position
                        prev_window.close()

                    document.show()
                    return document
                except Exception:
                    # Open failed; ensure any windows opened by the document are closed.
                    document.main_window.close()
                    raise

    async def save(self):
        """Save the current content of an app.

        If there isn't a current window, or current window doesn't define a `save()`
        method, the save request will be ignored.
        """
        if hasattr(self.app.current_window, "save"):
            await self.app.current_window.save()

    async def save_as(self):
        """Save the current content of an app under a different filename.

        If there isn't a current window, or the current window hasn't defined a
        `save_as()` method, the save-as request will be ignored.
        """
        if hasattr(self.app.current_window, "save_as"):
            await self.app.current_window.save_as()

    async def save_all(self):
        """Save the state of all content in the app.

        This method will attempt to call `save()` on every window associated with the
        app. Any windows that do not provide a `save()` method will be ignored.
        """
        for window in self.app.windows:
            if hasattr(window, "save"):
                await window.save()

types property

The list of document types the app can manage.

The first document type in the list is the app's default document type.

__init__(app, types)

A collection of documents managed by an app.

A document is automatically added to the app when it is created, and removed when it is closed. The document collection will be stored in the order that documents were created.

:param app: The app that this instance is bound to. :param types: The document types managed by this app.

Source code in core/src/toga/documents.py
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
204
205
206
207
208
209
def __init__(self, app: App, types: list[type[Document]]):
    """A collection of documents managed by an app.

    A document is automatically added to the app when it is created, and removed
    when it is closed. The document collection will be stored in the order that
    documents were created.

    :param app: The app that this instance is bound to.
    :param types: The document types managed by this app.
    """
    self.app = app
    for doc_type in types:
        if not hasattr(doc_type, "description"):
            raise ValueError(
                f"Document type {doc_type.__name__!r} "
                "doesn't define a 'descriptions' attribute"
            )
        if not hasattr(doc_type, "extensions"):
            raise ValueError(
                f"Document type {doc_type.__name__!r} "
                "doesn't define an 'extensions' attribute"
            )
        if len(doc_type.extensions) == 0:
            raise ValueError(
                f"Document type {doc_type.__name__!r} "
                "doesn't define at least one extension"
            )

    self._types = types

    self.elements: list[Document] = []

new(document_type)

Create a new document of the given type, and show the document window.

:param document_type: The document type that has been requested. :returns: The newly created document.

Source code in core/src/toga/documents.py
254
255
256
257
258
259
260
261
262
def new(self, document_type: type[Document]) -> Document:
    """Create a new document of the given type, and show the document window.

    :param document_type: The document type that has been requested.
    :returns: The newly created document.
    """
    document = document_type(app=self.app)
    document.show()
    return document

open(path)

Open a document in the app, and show the document window.

If the provided path is already an open document, the existing representation for the document will be given focus.

:param path: The path to the document to be opened. :returns: The document that was opened. :raises ValueError: If the path describes a file that is of a type that doesn't match a registered document type.

Source code in core/src/toga/documents.py
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
def open(self, path: Path | str) -> Document:
    """Open a document in the app, and show the document window.

    If the provided path is already an open document, the existing representation
    for the document will be given focus.

    :param path: The path to the document to be opened.
    :returns: The document that was opened.
    :raises ValueError: If the path describes a file that is of a type that doesn't
        match a registered document type.
    """
    path = Path(path).resolve()

    try:
        document = self.app.documents[path]
        document.focus()
        return document
    except KeyError:
        # No existing representation for the document.
        try:
            DocType = {
                extension: doc_type
                for doc_type in self.types
                for extension in doc_type.extensions
            }[path.suffix[1:]]
        except KeyError as exc:
            raise ValueError(
                f"Don't know how to open documents with extension {path.suffix}"
            ) from exc
        else:
            prev_window = self.app.current_window
            document = DocType(app=self.app)
            try:
                document.open(path)

                # If the previous window is marked for replacement, close it; but
                # put the new document window in the same position as the previous
                # one.
                if getattr(prev_window, "_replace", False):
                    document.main_window.position = prev_window.position
                    prev_window.close()

                document.show()
                return document
            except Exception:
                # Open failed; ensure any windows opened by the document are closed.
                document.main_window.close()
                raise

request_open() async

Present a dialog asking the user for a document to open, and pass the selected path to DocumentSet.open().

:returns: The document that was opened. :raises ValueError: If the path describes a file that is of a type that doesn't match a registered document type.

Source code in core/src/toga/documents.py
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
async def request_open(self) -> Document:
    """Present a dialog asking the user for a document to open, and pass the
    selected path to [`DocumentSet.open()`][toga.documents.DocumentSet.open].

    :returns: The document that was opened.
    :raises ValueError: If the path describes a file that is of a type that doesn't
        match a registered document type.
    """
    # A safety catch: if app modal dialogs aren't actually modal (eg, macOS) prevent
    # a second open dialog from being opened when one is already active. Attach the
    # dialog instance as a private attribute; delete as soon as the future is
    # complete.
    if hasattr(self, "_open_dialog"):
        return

    # CLOSE_ON_LAST_WINDOW is a proxy for the GTK/Windows behavior of loading
    # content into the existing window. This is actually implemented by creating a
    # new window and disposing of the old one; mark the current window for cleanup
    current_window = self.app.current_window
    if self.app._impl.CLOSE_ON_LAST_WINDOW:
        if hasattr(self.app.current_window, "_commit"):
            if await self.app.current_window._commit():
                current_window._replace = True
            else:
                # The changes in the current document window couldn't be committed
                # (e.g., a save was requested, but then cancelled), so we can't
                # proceed with opening a new document.
                return

    self._open_dialog = dialogs.OpenFileDialog(
        self.app.formal_name,
        file_types=(
            list(itertools.chain(*(doc_type.extensions for doc_type in self.types)))
            if self.types
            else None
        ),
    )
    path = await self.app.dialog(self._open_dialog)
    del self._open_dialog

    try:
        if path:
            return self.open(path)
    finally:
        # Remove the replacement marker
        if hasattr(current_window, "_replace"):
            del current_window._replace

save() async

Save the current content of an app.

If there isn't a current window, or current window doesn't define a save() method, the save request will be ignored.

Source code in core/src/toga/documents.py
361
362
363
364
365
366
367
368
async def save(self):
    """Save the current content of an app.

    If there isn't a current window, or current window doesn't define a `save()`
    method, the save request will be ignored.
    """
    if hasattr(self.app.current_window, "save"):
        await self.app.current_window.save()

save_all() async

Save the state of all content in the app.

This method will attempt to call save() on every window associated with the app. Any windows that do not provide a save() method will be ignored.

Source code in core/src/toga/documents.py
379
380
381
382
383
384
385
386
387
async def save_all(self):
    """Save the state of all content in the app.

    This method will attempt to call `save()` on every window associated with the
    app. Any windows that do not provide a `save()` method will be ignored.
    """
    for window in self.app.windows:
        if hasattr(window, "save"):
            await window.save()

save_as() async

Save the current content of an app under a different filename.

If there isn't a current window, or the current window hasn't defined a save_as() method, the save-as request will be ignored.

Source code in core/src/toga/documents.py
370
371
372
373
374
375
376
377
async def save_as(self):
    """Save the current content of an app under a different filename.

    If there isn't a current window, or the current window hasn't defined a
    `save_as()` method, the save-as request will be ignored.
    """
    if hasattr(self.app.current_window, "save_as"):
        await self.app.current_window.save_as()