A frontend is what shows your session to a person: a
desktop window today, and perhaps a web page in the future. redsun ships one
frontend, for Qt.
The core of redsun knows nothing about windows. A frontend adds a session
class to subclass, such as QtSession, which starts
the toolkit and shows the views, and a
Frontend listing the placements
it can show. QtSession lives in redsun.qt because importing it imports the
toolkit, which a session without a window doesn't install.
A view says where it wants to be shown, and the frontend decides whether it
can. The core defines only the Placement base class; the Qt frontend defines
docks and menus. Point at a placement to read what it needs:
Where Qt puts a view
main windowMenuItem("File")an entry in the File menuHolds a QAction. The menu is made if the window has none of that name. In a session file: {menu: File}.ToolBarItem("Main")an entry in the Main toolbarHolds a QAction. The toolbar is made if the window has none of that name. In a session file: {toolbar: Main}.Dock("top")Holds a QWidget, in a dock against that edge. Docks given the same group on one edge are tabbed together. In a session file: left, right, top, bottom, or {dock: left, group: name}.Dock("bottom")Holds a QWidget, in a dock against that edge. Docks given the same group on one edge are tabbed together. In a session file: left, right, top, bottom, or {dock: left, group: name}.Dock("left")Holds a QWidget, in a dock against that edge. Docks given the same group on one edge are tabbed together. In a session file: left, right, top, bottom, or {dock: left, group: name}.Central()the main areaHolds a QWidget. When several views ask for it, each gets a tab. In a session file: central.Dock("right")Holds a QWidget, in a dock against that edge. Docks given the same group on one edge are tabbed together. In a session file: left, right, top, bottom, or {dock: left, group: name}.Holds a QAction. The menu is made if the window has none of that name. In a session file: {menu: File}.Holds a QAction. The toolbar is made if the window has none of that name. In a session file: {toolbar: Main}.Holds a QWidget, in a dock against that edge. Docks given the same group on one edge are tabbed together. In a session file: left, right, top, bottom, or {dock: left, group: name}.Holds a QWidget, in a dock against that edge. Docks given the same group on one edge are tabbed together. In a session file: left, right, top, bottom, or {dock: left, group: name}.Holds a QWidget, in a dock against that edge. Docks given the same group on one edge are tabbed together. In a session file: left, right, top, bottom, or {dock: left, group: name}.Holds a QWidget. When several views ask for it, each gets a tab. In a session file: central.Holds a QWidget, in a dock against that edge. Docks given the same group on one edge are tabbed together. In a session file: left, right, top, bottom, or {dock: left, group: name}.
In Frontend.requires, a frontend lists what each placement must hold: Qt asks
for a QWidget in a dock or in the centre, and a QAction in a menu or
toolbar.
The session checks every view before it builds anything; point at a check to
read what it asks:
What the session checks of a view
which placement?The placement given in the declaration, else the class's. A view class that names no placement is refused, since a component that is shown nowhere is a presenter.does the frontendshow it?The placement must be one the frontend lists in Frontend.requires. Qt lists Central, Dock, MenuItem and ToolBarItem.is the view thetype it needs?Qt needs a QWidget in a dock or in the centre, and a QAction in a menu or a toolbar.build the viewdoes the constructorstart right?Frontend.check_view runs here. Qt asks for a constructor that starts with (name: str, parent: QWidget).The placement given in the declaration, else the class's. A view class that names no placement is refused, since a component that is shown nowhere is a presenter.The placement must be one the frontend lists in Frontend.requires. Qt lists Central, Dock, MenuItem and ToolBarItem.Qt needs a QWidget in a dock or in the centre, and a QAction in a menu or a toolbar.Frontend.check_view runs here. Qt asks for a constructor that starts with (name: str, parent: QWidget).
which placement?The placement given in the declaration, else the class's. A view class that names no placement is refused, since a component that is shown nowhere is a presenter.does the frontendshow it?The placement must be one the frontend lists in Frontend.requires. Qt lists Central, Dock, MenuItem and ToolBarItem.is the view thetype it needs?Qt needs a QWidget in a dock or in the centre, and a QAction in a menu or a toolbar.build the viewdoes the constructorstart right?Frontend.check_view runs here. Qt asks for a constructor that starts with (name: str, parent: QWidget).The placement given in the declaration, else the class's. A view class that names no placement is refused, since a component that is shown nowhere is a presenter.The placement must be one the frontend lists in Frontend.requires. Qt lists Central, Dock, MenuItem and ToolBarItem.Qt needs a QWidget in a dock or in the centre, and a QAction in a menu or a toolbar.Frontend.check_view runs here. Qt asks for a constructor that starts with (name: str, parent: QWidget).
A view that fails a check is left out before anything is built, with a message
naming what the frontend shows instead:
Failed to build view 'stray': MyApp.stray asks to be attached as 'Route', which
Qt does not attach. It attaches: Central, Dock, MenuItem, ToolBarItem.
A placement set from a property can only be checked after the view is
built; that form is deprecated and goes in 0.16, so set it as a class
attribute or in the declaration. A session file's placement words, such as
left or central, are read by the frontend's read_placement; the core
knows none of them.
A Qt view's constructor starts with exactly (name: str, parent: QWidget),
and QtSession passes its main window as the parent
(How to place a view). QtSession also:
runs a widget's slots on the main thread unless a slot names another
builds the main window from an app-modelApplication holding the menus
and commands
restores the docks where the user left them
asks before closing when a component has unsaved changes
logs an exception no slot caught and keeps the window open, where the Qt
binding would end the process silently
closes and deletes every view at shutdown, after delivering waiting
signals, so a third-party widget can clean up in its closeEvent
The Qt binding is chosen with QT_API, read by
qtpy, never by a session file.
A hook acts at a fixed moment of a Qt session's life
without changing what the session builds. The five
hook points run in this order; point at one to read
what it receives:
The hook points of a Qt session
create_applicationmake the QApplicationyourselfCalled with the command-line arguments, when no QApplication exists yet. It returns the QApplication.configure_applicationset a style or a fontCalled with the QApplication, before any view is made.during_buildshow progress whilethe build runsCalled with the QApplication. It wraps the build steps, from services to report.confirm_closeanswer whether thewindow may closeCalled with nothing, when the window is asked to close. It answers in place of the question about unsaved changes.the window is shownand the event loop runsconfigure_main_viewchange the windowbefore it is shownCalled with the main window, in the presentation step, once the views are in place.Called with the command-line arguments, when no QApplication exists yet. It returns the QApplication.Called with the QApplication, before any view is made.Called with the QApplication. It wraps the build steps, from services to report.Called with nothing, when the window is asked to close. It answers in place of the question about unsaved changes.Called with the main window, in the presentation step, once the views are in place.
create_applicationmake the QApplicationyourselfCalled with the command-line arguments, when no QApplication exists yet. It returns the QApplication.configure_applicationset a style or a fontCalled with the QApplication, before any view is made.during_buildshow progress whilethe build runsCalled with the QApplication. It wraps the build steps, from services to report.confirm_closeanswer whether thewindow may closeCalled with nothing, when the window is asked to close. It answers in place of the question about unsaved changes.the window is shownand the event loop runsconfigure_main_viewchange the windowbefore it is shownCalled with the main window, in the presentation step, once the views are in place.Called with the command-line arguments, when no QApplication exists yet. It returns the QApplication.Called with the QApplication, before any view is made.Called with the QApplication. It wraps the build steps, from services to report.Called with nothing, when the window is asked to close. It answers in place of the question about unsaved changes.Called with the main window, in the presentation step, once the views are in place.
Install hooks shows how. A session with no
frontend calls no hook points, so it refuses a hook declared on it.
A session file names its frontend by the name the frontend is registered
under:
frontend:qt
Session.from_config builds on the class registered under that name, or,
without a frontend key, on the class you called it on. Frontends are
registered as entry points, the packaging feature through which an installed
package announces what it offers, in the redsun.frontends group:
Another package registers its session class the same way, with no change to
redsun. A component asking for SessionConfig
finds the registered name in its frontend field, or None.
A new frontend defines its placements, its Frontend and a session class
that shows the views (How to write a frontend).
It may override Frontend.check_view, to refuse a view class it can't build,
and Session.view_arguments, to add arguments to every view's constructor.
The thread and the delivery matter because presenters on other threads call a
view's slots, while most toolkits allow one thread only.
Step through such a call:
A presenter calls a view from another thread
A presenter working on a worker thread emits a signal connected to a view's slot.
presenter emitson a worker thread
presenter emitson a worker thread
The slot names no thread and neither does its class, so the session asks Frontend.thread_of, which answers the main thread for a QWidget. The call waits in a queue.
presenter emitson a worker threadthe call waitsin a queueThe slot names no thread and neither does its class, so the session asks Frontend.thread_of, which answers the main thread for a QWidget.The slot names no thread and neither does its class, so the session asks Frontend.thread_of, which answers the main thread for a QWidget.
presenter emitson a worker threadthe call waitsin a queueThe slot names no thread and neither does its class, so the session asks Frontend.thread_of, which answers the main thread for a QWidget.The slot names no thread and neither does its class, so the session asks Frontend.thread_of, which answers the main thread for a QWidget.
The session calls psygnal.emit_queued from the toolkit's event loop, as often as the views should follow the presenters.
presenter emitson a worker threadthe call waitsin a queueThe slot names no thread and neither does its class, so the session asks Frontend.thread_of, which answers the main thread for a QWidget.the event loop callspsygnal.emit_queuedThe session calls it from the toolkit's event loop, as often as the views should follow the presenters.The slot names no thread and neither does its class, so the session asks Frontend.thread_of, which answers the main thread for a QWidget.The session calls it from the toolkit's event loop, as often as the views should follow the presenters.
presenter emitson a worker threadthe call waitsin a queueThe slot names no thread and neither does its class, so the session asks Frontend.thread_of, which answers the main thread for a QWidget.the event loop callspsygnal.emit_queuedThe session calls it from the toolkit's event loop, as often as the views should follow the presenters.The slot names no thread and neither does its class, so the session asks Frontend.thread_of, which answers the main thread for a QWidget.The session calls it from the toolkit's event loop, as often as the views should follow the presenters.
The queued call runs, and the view's slot runs on the main thread, where Qt allows it.
presenter emitson a worker threadthe call waitsin a queueThe slot names no thread and neither does its class, so the session asks Frontend.thread_of, which answers the main thread for a QWidget.the view's slot runson the main threadthe event loop callspsygnal.emit_queuedThe session calls it from the toolkit's event loop, as often as the views should follow the presenters.The slot names no thread and neither does its class, so the session asks Frontend.thread_of, which answers the main thread for a QWidget.The session calls it from the toolkit's event loop, as often as the views should follow the presenters.
presenter emitson a worker threadthe call waitsin a queueThe slot names no thread and neither does its class, so the session asks Frontend.thread_of, which answers the main thread for a QWidget.the view's slot runson the main threadthe event loop callspsygnal.emit_queuedThe session calls it from the toolkit's event loop, as often as the views should follow the presenters.The slot names no thread and neither does its class, so the session asks Frontend.thread_of, which answers the main thread for a QWidget.The session calls it from the toolkit's event loop, as often as the views should follow the presenters.
Coroutine slots need nothing from the frontend, because every session sets the
backend that runs them when it starts its runtime. That's why a frontend's
start_runtime must call the start_runtime it overrides.