~ubuntu-branches/ubuntu/oneiric/kig/oneiric

« back to all changes in this revision

Viewing changes to DESIGN

  • Committer: Bazaar Package Importer
  • Author(s): Harald Sitter
  • Date: 2011-07-10 11:57:38 UTC
  • Revision ID: james.westby@ubuntu.com-20110710115738-gdjnn1kctr49lmy9
Tags: upstream-4.6.90+repack
ImportĀ upstreamĀ versionĀ 4.6.90+repack

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
EXPLANATION OF THE KIG DESIGN
 
2
=============================
 
3
 
 
4
1. Object system
 
5
----------------
 
6
 
 
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.
 
13
 
 
14
 
 
15
 
 
16
1.1 ObjectImp's:  Basic objects.
 
17
 
 
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. ).
 
26
 
 
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.
 
32
 
 
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.
 
37
 
 
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.
 
44
 
 
45
 
 
46
1.2 ObjectCalcer's: calculating ObjectImp's from other ObjectImp's
 
47
 
 
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.. ).
 
57
 
 
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.  
 
65
 
 
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.
 
77
 
 
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.
 
83
 
 
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.
 
94
 
 
95
 
 
96
1.3 ObjectType's: a specification of how to calculate an object.
 
97
 
 
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.
 
105
 
 
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.
 
109
 
 
110
 
 
111
1.4 ObjectHolder's: a link from the document to the hierarchy
 
112
 
 
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.
 
120
 
 
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.
 
124
 
 
125
 
 
126
1.5 ObjectDrawer: An intelligent struct keeping some data about how to
 
127
    draw an ObjectImp on screen.
 
128
 
 
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.
 
135
 
 
136
 
 
137
2. Interesting Issues
 
138
---------------------
 
139
 
 
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.
 
142
 
 
143
 
 
144
2.1 Text labels 
 
145
 
 
146
Text labels in Kig are designed in a pretty flexible
 
147
way.  I will explain all the classes involved.
 
148
 
 
149
2.1.1 TextImp
 
150
 
 
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.
 
158
 
 
159
2.1.2 TextType
 
160
 
 
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.
 
172
 
 
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
 
178
 
 
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.".
 
182
 
 
183
All this gives labels in Kig a lot of flexibility.
 
184
 
 
185
2.2 Locuses
 
186
 
 
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.
 
194
 
 
195
2.2.1 Constrained points
 
196
 
 
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.
 
202
 
 
203
2.2.2 The Implementation
 
204
 
 
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.
 
210
 
 
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.
 
219
 
 
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.
 
223
 
 
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.
 
240
 
 
241
2.2.3 The Rationale
 
242
 
 
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.
 
247
 
 
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.
 
253
 
 
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.
 
267
 
 
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.
 
271
 
 
272
The current Kig system correctly models all dependency relations to
 
273
the extent possible, while keeping a correct implementation.
 
274
 
 
275