Creating a full featured Plasma shell involves pulling together a number of standard components from libplasma into a single interface. There are also several optional components that can be used to improve the overall user experience.
Whether you are creating a full screen interface such as Plasma Desktop or Plasma Netbook, or an application helper like the Plasma KPart or Plasma Media Center, this tutorial, which documents each of these components and how they fit together, can get you started in the right direction.
Plasma is a model/view approach to user interfaces, much as the QGraphicsView framework it is based on. Nearly all of the user interface is kept on a canvas which can be thought of as an infinite Cartesian plane. This plane is called the "Corona", and is a QGraphicsScene subclass.
The Corona serves as a top-level manager for Containments, which are QGraphicsWidgets that manage collections of Applets. Containments are not nested, and Applet nesting is also discouraged due to being error-prone and nearly never actually needed. This creates a nice hierarchy of Corona->Containments->Applets within the scene. Note that "plain" QGraphicsWidgets may exist between Containments and Applets in the scene, and that they are transparent in terms of the Plasma hierarchy.
Within the scene, items are placed in the four quadrants of the plane according to their functions: full window / screen Containments are kept in quadrant III, panel Containments in quadrants II and IV (to accomodate their most natural direction of growth, e.g horizontal panel Containments grow horizontally more often that vertically so appear in quadrant II) and other items such as contents for popup windows in quadrant I (often referred to as "offscreen widgets").
To present this to the user, Views are created which show sections of the Corona. Views usually are set to display the geometry of a Containment or an offscreen widget. There is no correlation between the location of a View in the user interface or on the computer screen and where the contents it is viewing are on the screen. It is also possible to have more than one View showing the same area on the scene at once.
So the primary job of a Plasma shell is to provide a collection of Views on the Corona scene arranged in a way that makes sense for the application. The default layout of the items on the scene is also managed by the Plasma shell, but storing and loading once set up is handled by libplasma.
In addition to Views and the use of stock Containments and Applets, shells may want to provide customized Containments and/or Applets. These custom additions can themselves also be plugins (which can be shared with other Plasma shells or not) or built-in to the shell itself using the PluginLoader class.
There are also a host of optional components that a shell can provide that allow a more customized presentation, such as the AccessManager which allows the shell to provide a custom user interface for remote access requests, the DialogManager which forwards requests for dialogs such as configuration interfaces to be shown, etc.
Each of these components is covered in detail below.
Corona is a subclass of QGraphicsScene, however it provides several additional facilities:
Most Plasma shells will want to create a subclass of Corona, if only to reimplement the loadDefaultLayout() method which is called when there is no existing layout configuration to load (e.g. on first launch of the application).
This is also where most Plasma shells implement things such as adding new Containments in response to shell-specific features or arranging Containments on the scene if necessary by reimplementing the layoutContainments() slot.
Most Plasma shell implementations find most of the default implementations of the virtual methods in Corona satisfactory, but there are valid use cases for each of the virtual methods. Examples of how these are implemented in different shells can be seen quite well in Plasma Desktop, Plasma Netbook, Plasma Mobile and the Plasma KPart.
Plasma::View is a subclass of QGraphicsView. It has some peculiarities over its base class:
Containment implementations can have several types:
A Containment implementation manages the way the applets are laid out inside it, so it will have to react to new applets being added or removed into the containment, by connecting to the appletAdded(Plasma::Applet *applet) and appletRemoved(Plasma::Applet *applet) signals.
If the applets in the containment can grow in any direction they want, the containment will have a 'Plasma::Planar FormFactor, otherwise will be Plasma::Horizontal if they can grow only horizontally and Plasma::Vertical if they can grow only vertically.
A typical use case is to set a Planar FormFactor when the Location is Plasma::Floating, Plasma::Desktop or Plasma::FullScreen. Which will be Horizontal for a BottomEdge or TopEdge Location and Vertical for a LeftEdge or RightEdge Location.
Usually Applets, DataEngines, Services and Runners are loaded from plugins on disk (either .so libraries if written in C++, or Plasma Packages if scripted). In those cases, the plugins exist external to the shell itself and are loosely coupled. It is possible to create application-specific plugins by including the X-KDE-ParentApp= entry in their .desktop files, but sometimes this isn't enough. In some cases, it is most natural to create some plugins right from within the shell instead of relying on the standard plugin loading mechanisms.
This is most often the case with users of the Plasma KPart when they need to offer DataEngines or even Applets that are tightly coupled to internal data structures or classes.
For Plasma shells that do not need any special, non-standard plugin loading tied to internals, PluginLoader can be ignored as an implementation detail. For shells that do need to augment the standard plugin loading, Plasma::PluginLoader allows applications to do so with shell-specific internal processes in a way that is completely transparent to libplasma and all other Plasma plugins (e.g. DataEngines loaded that are used by Applets).
To take advantage of this facility, the shell must instantiate a subclass of Plasma::PluginLoader and pass it into Plasma::PluginLoader::setPluginLoader(PluginLoader* loader) prior to any calls to Corona, DataEngineManager, RunnerManager, etc. that result in plugin loading. As soon as the first plugin is loaded, if there is no PluginLoader registered the default PluginLoader will be created for the shell. Thus it is important to perform this process before anything else.
PluginLoader provides a standard pattern for each of the plugin types which follows:
By implementing these two methods for each plugin type of interest, shells can provide access to internal objects very easily. An example for DataEngines might look like this: