moved kdeaccessibility kdeaddons kdeadmin kdeartwork kdebindings kdeedu kdegames...
[kdeedu.git] / kig / DESIGN
blobfd88777929001296c134f3a0d1b466b03ea3eab1
1 EXPLANATION OF THE KIG DESIGN
2 =============================
4 1. Object system
5 ----------------
7 The Kig Object System is a design I'm particularly proud of.  It
8 started out pretty basic, but has undergone some major revisions, that
9 have proven very succesful.  Currently, I have just made one more
10 major change, and I think this will be the last majore change to it
11 for quite some time to come.  That's also why I'm writing this
12 explanation for other developers.
16 1.1 ObjectImp's:  Basic objects.
18 An ObjectImp represents the current state of an object in Kig.  It
19 keeps information about what type of object it is ( e.g. a line, a
20 point, a circle etc. ), and its exact data ( e.g. the center and
21 radius of the circle ).  It is *not* in any way aware of how the
22 object was calculated from its parents (e.g. is this a line that is
23 constructed as the parallel of another line, or as the line going
24 through two given points ? ) or how it is drawn on the window (
25 e.g. the thickness of the line, its color etc. ).
27 There is also the notion of BogusImp's in Kig.  These are special
28 kinds of ObjectImp's that *only* hold data.  They do not represent any
29 real object that can be drawn on a window.  Their use is *only* in
30 holding data for other objects to use.  Examples are StringImp,
31 IntImp, ConicImp etc.
33 There are a lot of ObjectImp's in Kig, most of them are in files
34 called *_imp.h and *_imp.cc or *_imp.cpp in the objects subdirectory.
35 Examples are PointImp, LineImp, ConicImp, CircleImp, CubicImp,
36 AngleImp etc.
38 There is also the concept of ObjectImpType's.  These identify a kind
39 of ObjectImp.  They carry information about the inheritance among the
40 different ObjectImp types, and some strings identifying them.  You can
41 get hold of the ObjectImpType of a certain ObjectImp by using its
42 type() method, you can also get hold of them by name using
43 ObjectImpFactory.
46 1.2 ObjectCalcer's: calculating ObjectImp's from other ObjectImp's
48 An ObjectCalcer is an object that represents an algorithm for
49 calculating an ObjectImp from other ObjectImp's.  It is also a node in
50 the dependency graph of a certain document. E.g. a LineImp can be
51 calculated from the two PointImp's it has to go through; every time
52 either of them moves, this calculation is redone.  In this case, there
53 would be an ObjectCalcer that keeps a reference to its two parents (
54 the ObjectCalcer's representing the points ), and that will calculate
55 its ObjectImp value every time it is asked to do so ( i.e. every time
56 one of its parents moves.. ).
58 Because of the complex relations that ObjectCalcer's hold to other
59 ObjectCalcer's and to other classes, they have been made
60 reference-counted.  This means that they keep a count internally of
61 how much times a pointer to them is held.  If this count reaches 0,
62 this means that nobody needs them anymore, and they delete themselves.
63 E.g. an ObjectCalcer always keeps a reference to its parents, to
64 ensure that those aren't deleted before it is deleted.  
66 In the inheritance graph of a document, the lowermost objects keep
67 references to their parents and those keep reference to their parents,
68 so that all of the top of the graph is kept alive.  Of course, someone
69 needs to keep a reference to the bottommost objects in the graph,
70 because otherwise, the entire graph would be deleted.  As we will see
71 later, an external class ( ObjectHolder ) keeps a reference to the
72 ObjectCalcer's that the user is aware of.  Thus, the reference
73 counting system makes sure that all the objects that the user knows
74 about, and all of their ancestors are kept alive, and the others die.
75 At the end of the program, this reference is released, and all the
76 objects are deleted.
78 A special case of an ObjectCalcer is the ObjectConstCalcer.  This is
79 an ObjectCalcer that has no parents, and only holds some data.  The
80 data is held as an ObjectImp of some type, and it will remain
81 constant, and no calculation needs to be done to get it, it is just
82 returned every time it is needed.
84 Other ObjectCalcer's are ObjectPropertyCalcer and ObjectTypeCalcer.
85 ObjectTypeCalcer is a ObjectCalcer that calculates an object according
86 to what a ObjectType object specifies.  It basically forwards all
87 calculations to that object ( check below ).  An ObjectPropertyCalcer
88 gets data from a property of a certain object.  In fact, ObjectImp's
89 can specify property's ( e.g. properties of a circle are its radius,
90 its circumference, its center etc. An angle has its bisector as a
91 LineImp property ), and they are returned as ObjectImp's of an
92 appropriate type.  The ObjectPropertyCalcer just gets one of the
93 properties of a certain ObjectImp and stores it.
96 1.3 ObjectType's: a specification of how to calculate an object.
98 An ObjectType represents a certain algorithm to calculate an ObjectImp
99 from other ObjectImp's.  Unlike an ObjectCalcer, it does not
100 participate in the inheritance graph, and there is only one
101 instantiation of each type of ObjectType.  An ObjectTypeCalcer is an
102 ObjectCalcer that keeps a pointer to a certain ObjectType, and
103 forwards all requests it gets to its ObjectType.  It's very normal
104 that multiple ObjectTypeCalcer's share the same ObjectType.
106 There are very much ObjectType's in Kig, check out all of the files
107 that end in *_type.* or *_types.* in the objects subdirectory of the
108 Kig source code.
111 1.4 ObjectHolder's: a link from the document to the hierarchy
113 An ObjectHolder represents an object as it is known to the document.
114 It keeps a pointer to an ObjectCalcer, where it gets its data ( the
115 ObjectImp that the ObjectCalcer holds ) from.  It also holds
116 information about how to draw this ObjectImp on the window, by keeping
117 a pointer to an ObjectDrawer ( see below ).  In its draw method, it
118 gets the ObjectImp from the ObjectCalcer, and passes it to the
119 ObjectDrawer, asking it to draw the ObjectImp on the window.
121 The document ( check the KigDocument class ) holds a list of these
122 ObjectHolder's.  This is its only link with the ObjectCalcer
123 dependency graph.  An ObjectHolder keeps a reference to its ObjectCalcer.
126 1.5 ObjectDrawer: An intelligent struct keeping some data about how to
127     draw an ObjectImp on screen.
129 An ObjectDrawer is used by an ObjectHolder to keep information about
130 how to draw an ObjectImp on the window.  It is really nothing more
131 than a struct with some convenience methods.  It does not have any
132 virtual methods, or have any complex semantics.  It keeps information
133 like the thickness of an object, its color, and whether or not it is
134 hidden.
137 2. Interesting Issues
138 ---------------------
140 Here, I explain some parts of the design that may at first look
141 difficult to understand.  This part assumes you have read the above.
144 2.1 Text labels 
146 Text labels in Kig are designed in a pretty flexible
147 way.  I will explain all the classes involved.
149 2.1.1 TextImp
151 First of all, there is the TextImp class.  It is an ObjectImp (
152 cf. supra ), and thus represents a piece of text that can be drawn on
153 the document.  It contains a QString ( the text to be shown ), a
154 coordinate ( the location to draw it ), and a boolean saying whether a
155 frame should be drawn around it.  As with all ObjectImp's, it does not
156 contain any code for calculating it, or how it behaves on user input.
157 Most of this is handled by the TextType class.
159 2.1.2 TextType
161 The TextType class is an implementation of an ObjectType.  It contains
162 code specifying how to calculate a TextImp from its parents, and for
163 how it behaves on user input.  A text object has at least three
164 parents, and can handle any number of optional arguments.  The three
165 mandatory arguments are an int, which is set to 1 or 0 depending on
166 whether the label needs a surrounding box, a PointImp, containing the
167 location of the text label, and a string containing the text of the
168 label.  The text can contain tokens like '%1', '%2' etc.  Every
169 additional argument is used to replace the lowest-numbered of those
170 tokens, with its string representation.  The function
171 ObjectImp::fillInNextEscape is used for this.
173 For example, if a TextType has the following parents:
174 a IntImp with value 0
175 a PointImp with value (0,0)
176 a String with value "This segment is %1 units long."
177 a DoubleImp with value 3.9
179 This would result in a string being drawn at the coordinate (0,0),
180 with no surrounding box, and showing the text "This segment is 3.9
181 units long.".
183 All this gives labels in Kig a lot of flexibility.
185 2.2 Locuses
187 Locuses are a mathematical concept that has been modelled in Kig.
188 Loosely defined, a locus is the mathematical shape defined by the set
189 of points that a certain point moves through while another point is
190 moved over its constraints.  This can be used to define mathematical
191 objects like conics, and various other things.  It has been modelled
192 in Kig in the most flexible way I can imagine, and I must say that I'm
193 proud of this design.
195 2.2.1 Constrained points
197 In the implementation of this, we use the concept of constrained
198 points.  This is a point that is attached to a certain curve.  It is
199 implemented in Kig by the ConstrainedPointType, which takes a CurveImp
200 and a DoubleImp as parents and calculates a Point from these by using
201 the CurveImp::getPoint function.
203 2.2.2 The Implementation
205 When a Locus is constructed by the user, Kig receives two points, at
206 least one of which is a Constrained point, and the other one somehow
207 depends on the first.  This is checked before trying to construct a
208 Locus, and the user is not allowed to try to construct locuses from
209 other sorts of points.
211 Next, Kig takes a look at the ObjectCalcer hierarchy.  We look at the
212 smallest part of the hierarchy that contains all paths from the first
213 point to the second point.  We then determine all objects that are not
214 *on* one of those paths ( meaning that they are not calculated from
215 the first point, or another object that is on one of those paths ),
216 but that are parents of one or more objects that are on those paths.
217 I call this set of objects the "side of the path" sometimes in the
218 code.  The function that finds them is called sideOfTreePath.
220 Next, an ObjectHierarchy object is constructed, which stores the way
221 to calculate the second point from the first point and the objects
222 from the previous paragraph.
224 An object is then constructed that has as parent the curve parent that
225 the first point is constrained to, the HierarchyImp containing the
226 ObjectHierarchy from the previous paragraph, and all the objects from
227 the "side of the tree".  This new object is an ObjectTypeCalcer with
228 the LocusType as its type.  In its calc() function, it calculates a
229 LocusImp by taking the objecthierarchy and substituting all the
230 current values of the objects from the "side of the path", resulting
231 in an ObjectHierarchy that takes one PointImp and calculates another
232 PointImp from that.  The LocusImp then contains the new
233 ObjectHierarchy and the current value of the curve that the first
234 point is constrained to.  In the drawing function of this LocusImp,
235 points on the curve are calculated, and then the hierarchy is used to
236 calculated from those points the location of the second point.  A
237 dynamic feedback algorithm, which has been written with a lot of help
238 from the mathematician "Franco Pasquarelli" is used to determine which
239 of the points on the curve should be used.
241 2.2.3 The Rationale
243 The above explanation may seem very complicated, but I am very much
244 convinced that this *is* the proper way to handle locuses.  I will
245 here try explain why I think it is superior to the much simpler
246 implementation that is used by much other programs.
248 The basic alternative implementation involves just keeping a pointer
249 to the first and second point in the locus object, and when the locus
250 is drawn, the first point is moved over all its possible locations,
251 the second point is calculated, and a point is drawn at its new
252 location.
254 The reason I think that this is a bad implementation is that it is not
255 possible to model the real dependency relations properly in this
256 scheme.  For example, the locus object would then be made dependent on
257 the constrained point.  This is wrong because when the constrained
258 point moves within the limits of the curve constraining it, the locus
259 does by definition not change.  Also, if the constrained point is
260 redefined so that it is no longer constrained to any curve, this is a
261 major problem, because it would invalidate the locus.  Another point
262 is that in practice, the locus depends on more objects than its
263 parents alone.  This is not a good thing, because it makes it
264 impossible to optimise drawing of the objects, using the information
265 about which objects depend on which others, because this information
266 is invalid.
268 The reason we need to calculate the "side of the path" above is that,
269 together with the curve that the first point is constrained to, these
270 are the objects that the locus is really dependent on.
272 The current Kig system correctly models all dependency relations to
273 the extent possible, while keeping a correct implementation.