1 | /*
|
---|
2 | * Project: MoleCuilder
|
---|
3 | * Description: creates and alters molecular systems
|
---|
4 | * Copyright (C) 2010 University of Bonn. All rights reserved.
|
---|
5 | * Please see the LICENSE file or "Copyright notice" in builder.cpp for details.
|
---|
6 | */
|
---|
7 |
|
---|
8 | /**
|
---|
9 | * \file qt-gui.dox
|
---|
10 | *
|
---|
11 | * Created on: Jan 5, 2012
|
---|
12 | * Author: heber
|
---|
13 | */
|
---|
14 |
|
---|
15 | /**
|
---|
16 | * \page qt-gui Qt GUI
|
---|
17 | *
|
---|
18 | * The Qt GUI is the most advanced interface and thus the most complex.
|
---|
19 | *
|
---|
20 | * In the following we want to explain some of the details that are involved.
|
---|
21 | *
|
---|
22 | * \section qt-gui-general General Concepts
|
---|
23 | *
|
---|
24 | * Let us first discuss about the general concepts.
|
---|
25 | *
|
---|
26 | * MoleCuilder is about atoms, bonds and the molecules made up by them. But
|
---|
27 | * there is more: There are fragments, potentials, shapes, and so on.
|
---|
28 | *
|
---|
29 | * In the Qt GUI all of these are displayed in certain areas of the screen
|
---|
30 | * and also in a certain manner:
|
---|
31 | * -# the 3D view represents a three-dimensional representation of all atoms,
|
---|
32 | * and their bonds or possibly the molecules they form alone. Also the
|
---|
33 | * bounding box is shown and all selected shapes. Atoms or molecules can
|
---|
34 | * be selected by clicking. The view can be manipulated through rotation
|
---|
35 | * and translation.
|
---|
36 | * -# an element list shows all available elements of the period table.
|
---|
37 | * -# a molecule list shows all present molecules sorted by their formula.
|
---|
38 | * -# a fragment list shows all fragments with their energies and contributions
|
---|
39 | * -# a potential list shows all currently instantiated potentials and
|
---|
40 | * gives a 2D plot.
|
---|
41 | * -# a shape list displays all currently available shapes, allows to select
|
---|
42 | * them and buttons allow to combine them via boolean operation.
|
---|
43 | * -# an info box informs about the current atom/molecule the mouse pointer
|
---|
44 | * is hovering over.
|
---|
45 | *
|
---|
46 | * So, there are many objects that need to be filled with information and
|
---|
47 | * they need to access the World and other singletons in order to obtain
|
---|
48 | * this information.
|
---|
49 | *
|
---|
50 | * One major obstacle, or rather THE major obstacle, is that Qt is threaded,
|
---|
51 | * i.e. the Actions are processed in one thread and the Gui does its event
|
---|
52 | * processing in another one. Qt's Signal/Slot system is handled via this
|
---|
53 | * event system, i.e. a signal launched by one thread may be handled by
|
---|
54 | * the slot function in another thread. The Observer/Observable system
|
---|
55 | * of the CodePatterns which we used internally/outside Qt's scope does
|
---|
56 | * not do this.
|
---|
57 | *
|
---|
58 | * Also, signals may get delayed. This can happen either deliberately, e.g.
|
---|
59 | * there is a QTimer that only updates an object in regular intervals, or
|
---|
60 | * because of asynchronous threads. Elsewhen, the slot callback for a
|
---|
61 | * certain signal is called directly. All of these cases we have to
|
---|
62 | * accommodate. This is especially problematic with the instantiation and
|
---|
63 | * destruction of objects that represent atoms and molecules in the World.
|
---|
64 | *
|
---|
65 | * A clarifying example: Imagine an atom is constructed, the AtomObserver
|
---|
66 | * notifies about it, but the information is not processed immediately.
|
---|
67 | * Shortly after, the atom is destroyed again before its representation is
|
---|
68 | * instantiated in the GUI. Afterwards the GUI attempts to instantiate it
|
---|
69 | * but can not longer access the atom for its position and element.
|
---|
70 | *
|
---|
71 | * The only possible way out is to duplicate information. This is the usual
|
---|
72 | * way how to proceed in environments with multiple threads. I.e. all the
|
---|
73 | * information that the GUI representants of information inside the World
|
---|
74 | * needs to be doubled such that when the original information is destroyed
|
---|
75 | * the representant can still be accessed as long as needed. Here, we use
|
---|
76 | * the ObservedValue construct of CodePatterns.
|
---|
77 | *
|
---|
78 | * \subsection qt-gui-general-observedvalue Observed Value
|
---|
79 | *
|
---|
80 | * These representants are called \a ObservedValue in CodePatterns and they
|
---|
81 | * are used everywhere in the Qt Gui.
|
---|
82 | *
|
---|
83 | * They contain an internal information, e.g. a boolean, a Vector or even
|
---|
84 | * a complex structure such as a Tesselation. They require an updater
|
---|
85 | * function to obtain the derived information from the original source. And
|
---|
86 | * they signOn to the source in order to be notified either generally on
|
---|
87 | * updates or for specific channels only.
|
---|
88 | *
|
---|
89 | * The ObservedValue will automatically and immediately update its internal
|
---|
90 | * representation of the derived information by calling the updater function
|
---|
91 | * as soon as it has been informed about the update. Hence, the internal
|
---|
92 | * information is always up-to-date and lives beyond the scope of the
|
---|
93 | * source of the information until its own destruction. As updates are
|
---|
94 | * processed immediately, this pattern only makes sense for "small" pieces
|
---|
95 | * of information, i.e. when the updater function is very light-weight and
|
---|
96 | * does not do much in terms of using computing resources.
|
---|
97 | *
|
---|
98 | * Note that there is another concept that is opposite to the observed value,
|
---|
99 | * namely the Cacheable. This pattern will update itself only when requested,
|
---|
100 | * referred to as "lazy evaluation". Hence, this pattern is used for "large"
|
---|
101 | * pieces of information that require more computing resources within the
|
---|
102 | * updater. Also, the Cacheable's information can only be obtained as long
|
---|
103 | * as the source of information is still alive. However, so far Cacheable's
|
---|
104 | * content is marked invalid when an update signal has been received and
|
---|
105 | * update itself only on request, which is no longer possible when the object
|
---|
106 | * to represent is gone.
|
---|
107 | *
|
---|
108 | * Both concepts can be used in threaded environments as mutexes are used to
|
---|
109 | * protect read and write accesses.
|
---|
110 | *
|
---|
111 | * \subsection qt-gui-general-observedinstance The observed instance board
|
---|
112 | *
|
---|
113 | * The setup is then as follows: We have two distinct realms, the World (with
|
---|
114 | * atoms and molecules) on the one side and the QtGUI (with visual
|
---|
115 | * representations of atoms and molecules) on the other side.
|
---|
116 | *
|
---|
117 | * There is an interface between this world such that the destruction of an
|
---|
118 | * atom does not directly invalidate its visual representation. This interface
|
---|
119 | * between the two realms is contained in the class QtObservedInstanceBoard,
|
---|
120 | * which is a singleton and is similar to the World instance in the World realm
|
---|
121 | * for the QtGui realm.
|
---|
122 | *
|
---|
123 | * All properties, e.g. the position of an element, relevant to the QtGUI are
|
---|
124 | * duplicated as ObservedValues. Properties associated to the same instance in
|
---|
125 | * the World, e.g. the same atom, are combined into a QtObservedAtom instance,
|
---|
126 | * and similarly QtObservedMolecule for molecule. All of these observed
|
---|
127 | * instances are placed into ObservedValuesContainer which are contained in
|
---|
128 | * the interface QtObservedInstanceBoard.
|
---|
129 | *
|
---|
130 | * The sequence of events is then as follows (here exemplified with an atom):
|
---|
131 | * -# an atom is created (World::createAtom()), the World notifies about it
|
---|
132 | * via its World::AtomInserted channel.
|
---|
133 | * -# QtObservedInstanceBoard is signOn()ed to this channel and instantiates
|
---|
134 | * a new QtObservedAtom which is placed into its respective
|
---|
135 | * ObservedValuesContainer.
|
---|
136 | * -# on instantiation of QtObservedAtom a vector of specific ObservedValues is
|
---|
137 | * created, one for each property (position, element, bond count, ...).
|
---|
138 | * Each signOn()s to the respective atom's channel. Also the QtObservedAtom
|
---|
139 | * signOn()s to each of these channels as it converts these notifications
|
---|
140 | * into Qt signals (and the updated value can be accessed via getters from
|
---|
141 | * the QtObservedAtom instance). The QtObservedInstanceBoard is notified
|
---|
142 | * of this with the instance being marked as connected.
|
---|
143 | * -# when the atom is destroyed (World::destroyAtom()), being an Observable
|
---|
144 | * it will call subjectKilled() on all its channels. The
|
---|
145 | * ObservedValue_wCallback announces this subjectKilled() via the callback
|
---|
146 | * function which notifies QtObservedAtom. Once all subjectKilled(), for
|
---|
147 | * each observed value and for QtObservedAtom itself, have been received,
|
---|
148 | * the QtObservedInstanceBoard is notified by the instance now being
|
---|
149 | * marked as disconnected and ready for erase.
|
---|
150 | * -# then the QtObservedInstanceBoard removes the instance from its
|
---|
151 | * ObservedValuesContainer.
|
---|
152 | *
|
---|
153 | * Note however that the instance is a shared_ptr and will continue to exist
|
---|
154 | * and therefore its getters will still deliver the last piece of information
|
---|
155 | * before the atom was destroyed until all shared_ptrs are released. Hence,
|
---|
156 | * the QtGui may safely continue using the pointer.
|
---|
157 | *
|
---|
158 | * As new observed instances may come in immediately having the same id and
|
---|
159 | * as it is difficult to keep track who got its observed instance already
|
---|
160 | * and who not, a soft fail is required. I.e. of the QtObservedInstanceBoard
|
---|
161 | * returns an empty shared_ptr this means that the object -- despite any
|
---|
162 | * received (and probably delayed) signal -- has been destroyed and should
|
---|
163 | * not be displayed, updated by, ... whatsoever.
|
---|
164 | *
|
---|
165 | * \subsection qt-gui-general-signalslot Details on the slot connections
|
---|
166 | *
|
---|
167 | * Qt's event system does not guarantee that events are always processed in
|
---|
168 | * the order they are emitted. This is because connections can be defined
|
---|
169 | * as direct or queued (or both with auto). Direct connections will always
|
---|
170 | * be executed as direct function calls, i.e. immediately. Queued connections
|
---|
171 | * however are inserted into Qt's event queue and may even get processed by
|
---|
172 | * a different thread.
|
---|
173 | *
|
---|
174 | * We have to take care of this.
|
---|
175 | *
|
---|
176 | * Basically what we do in QtObservedInstanceBoard and the observed instances
|
---|
177 | * of type QtObservedAtom and QtObservedMolecule is that we translate between
|
---|
178 | * the Observer/Observable (O/O) system of CodePatterns with its callback
|
---|
179 | * functions and the event system of Qt with its Signal/Slot (S/S).
|
---|
180 | *
|
---|
181 | * That is in the recieveNotification() functions many "emit()"s can be found.
|
---|
182 | *
|
---|
183 | * Furthermore, signals are used in a specific way to ensure synchronicity.
|
---|
184 | * This is only a problem with the visual representation as we there find a
|
---|
185 | * a nested problem: First molecules, then atoms belonging to a certain
|
---|
186 | * molecule. This enforces a certain sequence of events and thus of signals.
|
---|
187 | *
|
---|
188 | * \subsubsection qt-gui-general-signalslot-glworldscene Details on GLWorldScene
|
---|
189 | *
|
---|
190 | * The central place for all events is the GLWorldScene instance. There
|
---|
191 | * signals from QtObservedInstanceBoard for insertion and removal of both atoms
|
---|
192 | * and molecules are caught. Insertion of molecules is dealt with directly,
|
---|
193 | * we sign on to the inserted&removed channels for its atoms, then we emit
|
---|
194 | * a queued signal to actually instantiate the GLMoleculeObject_molecule.
|
---|
195 | *
|
---|
196 | * Until its instantiation we store incoming signals in the
|
---|
197 | * GLWorldScene::MoleculeMissedStateMap, protected by a mutex to enforce atomic
|
---|
198 | * access. After it has been instantiated (and all stored signals have been
|
---|
199 | * processed), they are relayed onto the specific instance. However, we do not
|
---|
200 | * do this via emits but by directly using Qt's invokeMethod() which allows
|
---|
201 | * to trigger queued events. This way it is done in a likewise manner whether
|
---|
202 | * it has been a stored or live signal that was received.
|
---|
203 | *
|
---|
204 | * \subsubsection qt-gui-general-signalslot-other Details on other signals
|
---|
205 | *
|
---|
206 | * All other signals that only change the property of a visual representation,
|
---|
207 | * e.g. the element of an atom, is directly processed by, in this case, the
|
---|
208 | * GLMoleculeObject_atom connected to QtObservedAtom.
|
---|
209 | . *
|
---|
210 | * \section qt-gui-qt3d Qt3D and the way to get atoms and bonds displayed
|
---|
211 | *
|
---|
212 | * By far the most difficult component of the Qt GUI is the 3D view. So,
|
---|
213 | * let us explain it in detail.
|
---|
214 | *
|
---|
215 | * The general widget making up the view is called \a GLWorldView. It contains
|
---|
216 | * the GLWorldScene (i.e. all atoms, bonds, molecules, and shapes). Also
|
---|
217 | * the "dreibein" and the domain. It processes key presses and mouse events
|
---|
218 | * to manipulate the view. And it also serves as the translator O/O to S/S
|
---|
219 | * system.
|
---|
220 | *
|
---|
221 | * The GLWorldScene contains the actual nodes of the molecular system, i.e.
|
---|
222 | * the atoms, bonds, molecules, and shapes. All of these are derived from
|
---|
223 | * GLMoleculeObject and have their parent to the instance of the GLWorldScene
|
---|
224 | * which goes through its list of children and to call draw() on them.
|
---|
225 | *
|
---|
226 | * The bottom-most structure is GLMoleculeObject_atom displaying a sphere
|
---|
227 | * of an element-specific color at the atom's position. The atom relies
|
---|
228 | * on its representants to be contain all required information but it
|
---|
229 | * is also signOn() to the QtObservedAtom itself whose O/O are translated to
|
---|
230 | * S/S for processing whenever desired.
|
---|
231 | *
|
---|
232 | * Next comes the GLMoleculeObject_bond which displays a cylinder between
|
---|
233 | * two atoms. Actual, a true bond consists of two of these objects. If the
|
---|
234 | * bond is between heterogeneous atoms each half will be displayed in the
|
---|
235 | * color of the closer atom. These bond objects are not associated with
|
---|
236 | * the atoms directly as the are linked to two atoms at the same time. They
|
---|
237 | * rely on ObservedValues for position and element of either atom and for
|
---|
238 | * the degree of the bond itself.
|
---|
239 | *
|
---|
240 | * Parallel to these are GLMoleculeObject_shape which display the surface
|
---|
241 | * of a selected shape. A shape in general does not change after instantiation,
|
---|
242 | * hence the shape lives with the information it gets on instantiation till
|
---|
243 | * it dies.
|
---|
244 | *
|
---|
245 | * Finally, the GLMoleculeObject_molecule owns both atoms and bonds. This
|
---|
246 | * allows for switching the view between the classical ball-and-stick model
|
---|
247 | * and the tesselated surface of the molecule. The latter uses a lot less
|
---|
248 | * triangles and thus is faster. Also, it is especially suited for large
|
---|
249 | * molecules. The molecule also needs ObservedValues for its bounding box
|
---|
250 | * (used to show when it's selected), the index, the selection status,
|
---|
251 | * and the list of atom ids.
|
---|
252 | *
|
---|
253 | * \section qt-gui-cases Sample cases
|
---|
254 | *
|
---|
255 | * Let us discuss some cases and how the different instances interact.
|
---|
256 | *
|
---|
257 | * \section qt-gui-cases-start Start
|
---|
258 | *
|
---|
259 | * When molecuilder is started, several singletons such as the World and
|
---|
260 | * others are instantiated. No atoms are yet present, no bonds, no molecules.
|
---|
261 | * Hence, nothing to display yet.
|
---|
262 | *
|
---|
263 | * Before launching any Action the ActionQueue is forced to wait till the
|
---|
264 | * GUI is finished instantiating. This is to ensure that GLWorldView and
|
---|
265 | * others are in place to receive signals from the O/O system.
|
---|
266 | *
|
---|
267 | * When a molecule is loaded, the instantiation of a GLMoleculeObject_molecule
|
---|
268 | * does not happen immediately. Hence, GLWorldView listens to the World's
|
---|
269 | * MoleculeInserted. On receiving it, it also signOn()s to the molecule
|
---|
270 | * to get its subjectKilled(). It translates then these and also all
|
---|
271 | * AtomInserted and AtomRemoved to the S/S system as moleculeInserted,
|
---|
272 | * moleculeRemoved and atomInserted/atomRemoved respectively, which are
|
---|
273 | * processed by the GLWorldScene.
|
---|
274 | *
|
---|
275 | * The GLWorldScene records any atomInserted/atomRemoved until the molecule
|
---|
276 | * has been instantiated. On instantiation all recorded events are played.
|
---|
277 | * This is to ensure that there is no overlap in instantiation and signOn()
|
---|
278 | * to the molecule. If we would simply get all atoms which are present
|
---|
279 | * on processing the molecule's instantiation we might stumble over a signal
|
---|
280 | * of a molecule of a just added atom. This occurs frequently as both
|
---|
281 | * are very much correlated.
|
---|
282 | *
|
---|
283 | * GLWorldView keep track of all ObservedMolecules. And GLWorldScene keeps
|
---|
284 | * track of all shapes and molecules in the scene. Each
|
---|
285 | * GLMoleculeObject_molecule in turn keeps track of all atoms and bonds in
|
---|
286 | * its part of the scene.
|
---|
287 | *
|
---|
288 | * \section QtElementList
|
---|
289 | *
|
---|
290 | * Lists for each element how often it occurs in the world. Selecting an entry
|
---|
291 | * calls SelectionAtomByElementAction to select all atoms of that particular
|
---|
292 | * element.
|
---|
293 | *
|
---|
294 | * Initially, it fills itself by looking at all elements in the World's
|
---|
295 | * periodentafel. It also listens to AtomObserver's ElementChanged to know
|
---|
296 | * when to update a certain element in its list. By using an internal list
|
---|
297 | * for each atom's element, it can update each element's occurrence.
|
---|
298 | *
|
---|
299 | * \section QtMoleculeList
|
---|
300 | *
|
---|
301 | * Lists all the molecules currently in the world grouped by their formula.
|
---|
302 | * Selecting an entry calls the SelectionMoleculeByIdAction.
|
---|
303 | *
|
---|
304 | * The QtMoleculeList is also a rather complex beast. It is a tree of
|
---|
305 | * rows and each row consists of a number of elements. There are two
|
---|
306 | * levels, the group level where the common formula for all molecules
|
---|
307 | * is given, and the molecule level where are molecules of this specific
|
---|
308 | * formula are summarized.
|
---|
309 | *
|
---|
310 | * The group items are QStandardItems. Sadly, they are not derived from
|
---|
311 | * QObject and hence do not use the S/S system. The group items are
|
---|
312 | * directly controlled by the QtMoleculeList.
|
---|
313 | *
|
---|
314 | * However, the molecule items are different. They are derived from
|
---|
315 | * QtMoleculeList and use an ObservedValue internally to contain an always
|
---|
316 | * valid copy of the required information. They inform the QtMoleculeList on
|
---|
317 | * updates via a callback (as QStandardItem, from which they are also derived,
|
---|
318 | * does not use the S/S system). The callback takes care of then also updating
|
---|
319 | * the group items and possibly moving the molecule items around, e.g. if
|
---|
320 | * their formula has changed they suddenly belong to another group.
|
---|
321 | *
|
---|
322 | * All items are instantiated by the QtMoleculeItemFactory.
|
---|
323 | *
|
---|
324 | * QtMoleculeList uses an internal QTimer to only update itself at regular
|
---|
325 | * intervals. Hence, updates are processed rather lazily. We keep lists
|
---|
326 | * of changes, separated for group and molecule items. And these are processed
|
---|
327 | * one after the other at the intervals dictated by the QTimer in
|
---|
328 | * updateItemStates().
|
---|
329 | *
|
---|
330 | * \section QtShapeController
|
---|
331 | *
|
---|
332 | * This is the interface for the ShapeRegistry. It lists all the shapes in the
|
---|
333 | * registry and lets the user select them. It also features buttons to call
|
---|
334 | * actions creating and manipulating the selected shapes.
|
---|
335 | *
|
---|
336 | * As an Observer it handles the following messages from ShapeRegistry:
|
---|
337 | * - ShapeRegistry::ShapeInserted
|
---|
338 | * - ShapeRegistry::ShapeRemoved
|
---|
339 | * - ShapeRegistry::SelectionChanged
|
---|
340 | *
|
---|
341 | * \section QtInfoBox
|
---|
342 | *
|
---|
343 | * Shows information about the atom and molecule the cursor is currently hovering
|
---|
344 | * over inside the GLWorldView.
|
---|
345 | *
|
---|
346 | * GLWorldView emits hoverChanged signals (via QT's signal slot mechanism) which
|
---|
347 | * the QtInfoBox receives. QtInfoBox then creates its info pages for the atom
|
---|
348 | * being transmitted as the signal's parameter.
|
---|
349 | *
|
---|
350 | * The info pages are Observers for the atom/molecule. When recieving subjectKilled
|
---|
351 | * they automatically clear the info box.
|
---|
352 | *
|
---|
353 | * \date 2016-01-08
|
---|
354 | */
|
---|