/* * Project: MoleCuilder * Description: creates and alters molecular systems * Copyright (C) 2010 University of Bonn. All rights reserved. * Please see the LICENSE file or "Copyright notice" in builder.cpp for details. */ /** * \file qt-gui.dox * * Created on: Jan 5, 2012 * Author: heber */ /** * \page qt-gui Qt GUI * * The Qt GUI is the most advanced interface and thus the most complex. * * In the following we want to explain some of the details that are involved. * * \section qt-gui-general General Concepts * * Let us first discuss about the general concepts. * * MoleCuilder is about atoms, bonds and the molecules made up by them. But * there is more: There are fragments, potentials, shapes, and so on. * * In the Qt GUI all of these are displayed in certain areas of the screen * and also in a certain manner: * -# the 3D view represents a three-dimensional representation of all atoms, * and their bonds or possibly the molecules they form alone. Also the * bounding box is shown and all selected shapes. Atoms or molecules can * be selected by clicking. The view can be manipulated through rotation * and translation. * -# an element list shows all available elements of the period table. * -# a molecule list shows all present molecules sorted by their formula. * -# a fragment list shows all fragments with their energies and contributions * -# a potential list shows all currently instantiated potentials and * gives a 2D plot. * -# a shape list displays all currently available shapes, allows to select * them and buttons allow to combine them via boolean operation. * -# an info box informs about the current atom/molecule the mouse pointer * is hovering over. * * So, there are many objects that need to be filled with information and * they need to access the World and other singletons in order to obtain * this information. * * One major obstacle, or rather THE major obstacle, is that Qt is threaded, * i.e. the Actions are processed in one thread and the Gui does its event * processing in another one. Qt's Signal/Slot system is handled via this * event system, i.e. a signal launched by one thread may be handled by * the slot function in another thread. The Observer/Observable system * of the CodePatterns which we used internally/outside Qt's scope does * not do this. * * Also, signals may get delayed. This can happen either deliberately, e.g. * there is a QTimer that only updates an object in regular intervals, or * because of asynchronous threads. Elsewhen, the slot callback for a * certain signal is called directly. All of these cases we have to be * accommodated for. This is especially problematic with the instantiation and * destruction of objects that represent atoms and molecules in the World. * * A clarifying example: Imagine an atom is constructed, the AtomObserver * notifies about it, but the information is not processed immediately. * Shortly after, the atom is destroyed again before its representation is * instantiated in the GUI. Afterwards the GUI attempts to instantiate it * but can not longer access the atom for its position and element. * * The only possible way out is to duplicate information. This is the usual * way how to proceed in environments with multiple threads. I.e. all the * information that the GUI representants of information inside the World * needs to be doubled such that when the original information is destroyed * the representant can still be accessed as long as needed. Here, we use * the ObservedValue construct of CodePatterns. * * \subsection qt-gui-general-observedvalue Observed Value * * These representants are called \a ObservedValue in CodePatterns and they * are used everywhere in the Qt Gui. * * They contain an internal information, e.g. a boolean, a Vector or even * a complex structure such as a Tesselation. They require an updater * function to obtain the derived information from the original source. And * they signOn to the source in order to be notified either generally on * updates or for specific channels only. * * The ObservedValue will automatically and immediately update its internal * representation of the derived information by calling the updater function * as soon as it has been informed about the update. Hence, the internal * information is always up-to-date and lives beyond the scope of the * source of the information until its own destruction. As updates are * processed immediately, this pattern only makes sense for "small" pieces * of information, i.e. when the updater function is very light-weight and * does not do much in terms of using computing resources. * * Note that there is another concept that is opposite to the observed value, * namely the Cacheable. This pattern will update itself only when requested, * referred to as "lazy evaluation". Hence, this pattern is used for "large" * pieces of information that require more computing resources within the * updater. Also, the Cacheable's information can only be obtained as long * as the source of information is still alive. However, so far Cacheable's * content is marked invalid when an update signal has been received and * update itself only on request, which is no longer possible when the object * to represent is gone. Hence, after destruction the information is no longer * accessible, not even in the Cacheable, at the current moment. * * Both concepts can be used in threaded environments as mutexes are used to * protect read and write accesses. * * \subsection qt-gui-general-observedinstance The observed instance board * * The setup is then as follows: We have two distinct realms, the World (with * atoms and molecules) on the one side and the QtGUI (with visual * representations of atoms and molecules) on the other side. * * There is an interface between this world such that the destruction of an * atom does not directly invalidate its visual representation. This interface * between the two realms is contained in the class QtObservedInstanceBoard, * which is a singleton and is similar to the World instance in the World realm * for the QtGui realm. * * All properties, e.g. the position of an element, relevant to the QtGUI are * duplicated as ObservedValues. Properties associated to the same instance in * the World, e.g. the same atom, are combined into a QtObservedAtom instance, * and similarly QtObservedMolecule for molecule. All of these observed * instances are placed into ObservedValuesContainer which are contained in * the interface QtObservedInstanceBoard. * * The sequence of events is then as follows (here exemplified with an atom): * -# an atom is created (World::createAtom()), the World notifies about it * via its World::AtomInserted channel. * -# QtObservedInstanceBoard is signOn()ed to this channel and instantiates * a new QtObservedAtom which is placed into its respective * ObservedValuesContainer. * -# on instantiation of QtObservedAtom a vector of specific ObservedValues is * created, one for each property (position, element, bond count, ...). * Each signOn()s to the respective atom's channel. Also the QtObservedAtom * signOn()s to each of these channels as it converts these notifications * into Qt signals (and the updated value can be accessed via getters from * the QtObservedAtom instance). The QtObservedInstanceBoard is notified * of this with the instance being marked as connected. * -# when the atom is destroyed (World::destroyAtom()), being an Observable * it will call subjectKilled() on all its channels. The * ObservedValue_wCallback announces this subjectKilled() via the callback * function which notifies QtObservedAtom. Once all subjectKilled(), for * each observed value and for QtObservedAtom itself, have been received, * the QtObservedInstanceBoard is notified by the instance now being * marked as disconnected and ready for erase. * -# then the QtObservedInstanceBoard removes the instance from its * ObservedValuesContainer. * * Note however that the instance is a shared_ptr and will continue to exist * and therefore its getters will still deliver the last piece of information * before the atom was destroyed until all shared_ptrs are released. Hence, * the QtGui may safely continue using the pointer. * * As new observed instances may come in immediately having the same id and * as it is difficult to keep track who got its observed instance already * and who not, a soft fail is required. I.e. of the QtObservedInstanceBoard * returns an empty shared_ptr this means that the object -- despite any * received (and probably delayed) signal -- has been destroyed and should * not be displayed, updated by, ... whatsoever. * * \subsection qt-gui-general-signalslot Details on the slot connections * * Qt's event system does not guarantee that events are always processed in * the order they are emitted. This is because connections can be defined * as direct or queued (or both with auto). Direct connections will always * be executed as direct function calls, i.e. immediately. Queued connections * however are inserted into Qt's event queue and may even get processed by * a different thread. * * We have to take care of this. * * Basically what we do in QtObservedInstanceBoard and the observed instances * of type QtObservedAtom and QtObservedMolecule is that we translate between * the Observer/Observable (O/O) system of CodePatterns with its callback * functions and the event system of Qt with its Signal/Slot (S/S). * * That is in the recieveNotification() functions many "emit()"s can be found. * * Furthermore, signals are used in a specific way to ensure synchronicity. * This is only a problem with the visual representation as we there find a * a nested problem: First molecules are created, then atoms belonging to a * certain molecule. And on destruction it is the other way round. This * enforces a certain sequence of events and thus of signals. * * \subsubsection qt-gui-general-signalslot-glworldscene Details on GLWorldScene * * The central place for all events is the GLWorldScene instance. There * signals from QtObservedInstanceBoard for insertion and removal of both atoms * and molecules are caught. Insertion of molecules is dealt with directly, * we sign on to the inserted&removed channels for its atoms, then we emit * a queued signal to actually instantiate the GLMoleculeObject_molecule. * * Until its instantiation we store incoming signals in the * GLWorldScene::MoleculeMissedStateMap, protected by a mutex to enforce atomic * access. After it has been instantiated (and all stored signals have been * processed), they are relayed onto the specific instance. However, we do not * do this via emits but by directly using Qt's invokeMethod() which allows * to trigger queued events. This way it is done in a likewise manner whether * it has been a stored or live signal that was received. * * \subsubsection qt-gui-general-signalslot-other Details on other signals * * All other signals that only change the property of a visual representation, * e.g. the element of an atom, is directly processed by, in this case, the * GLMoleculeObject_atom connected to QtObservedAtom. . * * \section qt-gui-qt3d Qt3D and the way to get atoms and bonds displayed * * By far the most difficult component of the Qt GUI is the 3D view. So, * let us explain it in detail. * * The general widget making up the view is called \a GLWorldView. It contains * the GLWorldScene (i.e. all atoms, bonds, molecules, and shapes). Also * the "dreibein" and the domain. It processes key presses and mouse events * to manipulate the view. And it also serves as the translator O/O to S/S * system. * * The GLWorldScene contains the actual nodes of the molecular system, i.e. * the atoms, bonds, molecules, and shapes. All of these are derived from * GLMoleculeObject and have their parent to the instance of the GLWorldScene * which goes through its list of children and to call draw() on them. * * The bottom-most structure is GLMoleculeObject_atom displaying a sphere * of an element-specific color at the atom's position. The atom relies * on its representants to be contain all required information but it * is also signOn() to the QtObservedAtom itself whose O/O are translated to * S/S for processing whenever desired. * * Next comes the GLMoleculeObject_bond which displays a cylinder between * two atoms. Actual, a true bond consists of two of these objects. If the * bond is between heterogeneous atoms each half will be displayed in the * color of the closer atom. These bond objects are not associated with * the atoms directly as the are linked to two atoms at the same time. They * rely on ObservedValues for position and element of either atom and for * the degree of the bond itself. * * Parallel to these are GLMoleculeObject_shape which display the surface * of a selected shape. A shape in general does not change after instantiation, * hence the shape lives with the information it gets on instantiation till * it dies. * * Finally, the GLMoleculeObject_molecule owns both atoms and bonds. This * allows for switching the view between the classical ball-and-stick model * and the tesselated surface of the molecule. The latter uses a lot less * triangles and thus is faster. Also, it is especially suited for large * molecules. The molecule also needs ObservedValues for its bounding box * (used to show when it's selected), the index, the selection status, * and the list of atom ids. * * \section qt-gui-cases Sample cases * * Let us discuss some cases and how the different instances interact. * * \section qt-gui-cases-start Start * * When molecuilder is started, several singletons such as the World and * others are instantiated. No atoms are yet present, no bonds, no molecules. * Hence, nothing to display yet. * * Before launching any Action the ActionQueue is forced to wait till the * GUI is finished instantiating. This is to ensure that GLWorldView and * others are in place to receive signals from the O/O system. * * When a molecule is loaded, the instantiation of a GLMoleculeObject_molecule * does not happen immediately. Hence, GLWorldView listens to the World's * MoleculeInserted. On receiving it, it also signOn()s to the molecule * to get its subjectKilled(). It translates then these and also all * AtomInserted and AtomRemoved to the S/S system as moleculeInserted, * moleculeRemoved and atomInserted/atomRemoved respectively, which are * processed by the GLWorldScene. * * The GLWorldScene records any atomInserted/atomRemoved until the molecule * has been instantiated. On instantiation all recorded events are played. * This is to ensure that there is no overlap in instantiation and signOn() * to the molecule. If we would simply get all atoms which are present * on processing the molecule's instantiation we might stumble over a signal * of a molecule of a just added atom. This occurs frequently as both * are very much correlated. * * GLWorldView keep track of all ObservedMolecules. And GLWorldScene keeps * track of all shapes and molecules in the scene. Each * GLMoleculeObject_molecule in turn keeps track of all atoms and bonds in * its part of the scene. * * \section QtElementList * * Lists for each element how often it occurs in the world. Selecting an entry * calls SelectionAtomByElementAction to select all atoms of that particular * element. * * Initially, it fills itself by looking at all elements in the World's * periodentafel. It also listens to AtomObserver's ElementChanged to know * when to update a certain element in its list. By using an internal list * for each atom's element, it can update each element's occurrence. * * \section QtMoleculeList * * Lists all the molecules currently in the world grouped by their formula. * Selecting an entry calls the SelectionMoleculeByIdAction. * * The QtMoleculeList is also a rather complex beast. It is a tree of * rows and each row consists of a number of elements. There are two * levels, the group level where the common formula for all molecules * is given, and the molecule level where are molecules of this specific * formula are summarized. * * The group items are QStandardItems. Sadly, they are not derived from * QObject and hence do not use the S/S system. The group items are * directly controlled by the QtMoleculeList. * * However, the molecule items are different. They are derived from * QtMoleculeList and use an ObservedValue internally to contain an always * valid copy of the required information. They inform the QtMoleculeList on * updates via a callback (as QStandardItem, from which they are also derived, * does not use the S/S system). The callback takes care of then also updating * the group items and possibly moving the molecule items around, e.g. if * their formula has changed they suddenly belong to another group. * * All items are instantiated by the QtMoleculeItemFactory. * * QtMoleculeList uses an internal QTimer to only update itself at regular * intervals. Hence, updates are processed rather lazily. We keep lists * of changes, separated for group and molecule items. And these are processed * one after the other at the intervals dictated by the QTimer in * updateItemStates(). * * \section QtShapeController * * This is the interface for the ShapeRegistry. It lists all the shapes in the * registry and lets the user select them. It also features buttons to call * actions creating and manipulating the selected shapes. * * As an Observer it handles the following messages from ShapeRegistry: * - ShapeRegistry::ShapeInserted * - ShapeRegistry::ShapeRemoved * - ShapeRegistry::SelectionChanged * * \section QtInfoBox * * Shows information about the atom and molecule the cursor is currently hovering * over inside the GLWorldView. * * GLWorldView emits hoverChanged signals (via QT's signal slot mechanism) which * the QtInfoBox receives. QtInfoBox then creates its info pages for the atom * being transmitted as the signal's parameter. * * The info pages are Observers for the atom/molecule. When recieving subjectKilled * they automatically clear the info box. * * \date 2016-02-13 */