[go: up one dir, main page]

Menu

[80c337]: / lchange.h  Maximize  Restore  History

Download this file

406 lines (365 with data), 18.5 kB

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
#ifndef LOB_CHANGE
#define LOB_CHANGE
#include <OmHeaders.h>
#include <QObject>
#include "lob.h"
/** \brief A class embodying an editing action (atomic or compound) on a Lob
*
* <h2>Atomic Changes</h2>
*
* There are three types of atomic editing actions one can do to a Lob:
* insert new content, remove content, and change existing content.
* Here they are in greater detail.
* <ol>
* <li>There are two different ways to insert new content, either inserting a
* child node of some existing node in the tree or inserting a new key-value
* pair to attribute an existing node in the tree.</li>
* <li>There are two different ways to remove content, corresponding to the
* inverses of the two insertion mechanisms. You can either remove an
* existing subtree or remove an existing key-value pair from a node's
* attribute list.</li>
* <li>Changing a subtree means replacing an existing subtree with a different one.
* Although this is equivalent to removing the old subtree and inserting the
* new one, I keep this as its own atomic operation because users editing
* interactively will want to think of such an action as one step. The
* changed node may be the tree root, a child, a key, or a value.</li>
* </ol>
*
* \anchor LobChangeTypes
* <h2>Change Types and Data</h2>
*
* Changes are recorded relative to a given root, and every LobChange object remembers
* that root and the address from the root to the subtree that changed. Specifically,
* the three types above are stored in a LobChange object according to the following
* details, and you can fetch this data using the functions mentioned here.
* <ol>
* <li>A LobChange object with type() == LobChange::Insert will know the address()
* where the new subtree was inserted, and its newData() will be the inserted Lob.</li>
* <li>A LobChange object with type() == LobChange::InsertPair will know the address()
* of the pair's value, and its newData() will be the inserted value, and
* keyName() and keyCD() will store the name and content dictionary for the key.</li>
* <li>A LobChange object with type() == LobChange::Remove will know the address()
* where the new subtree was before removal,
* and its oldData() will be the removed Lob.</li>
* <li>A LobChange object with type() == LobChange::RemovePair will know the address()
* where the pair's value was before removal,
* and its oldData() will be the removed value. The functions keyName() and keyCD()
* store the name and content dictionary for the removed key.</li>
* <li>A LobChange object with type() == LobChange::Change will know the address()
* of the subtree to change, and oldData() will be the subtree before the change
* while newData() will be the subtree after the change.</li>
* <li>A LobChange object with type() == LobChange::Compound is a sequence of atomic
* changes combined into one object. Use count() and step() to query the
* individual atomic changes.</li>
* <li>A LobChange object with type() == LobChange::NoChange is the null action.
* It is the identity element for operator+().</li>
* </ol>
*
* You can use apply() to perform a specified LobChange on a Lob (either the original
* or a different one, if you know what you're doing). You can record in a LobChange
* object edits as you perform them using startRecording(). You can combine two LobChange
* objects into a compound one with combine(), which is the same as operator+().
* Lastly, you can turn any LobChange into the corresponding "undo" action with inverse().
*/
class LobChange : public QObject
{
Q_OBJECT
public:
/** All LobChange objects are one of these types.
* \ref LobChangeTypes "See the LobChange class description for details on types."
*/
typedef enum { Insert, InsertPair, Remove, RemovePair, Change, Compound, NoChange } Type;
/** \brief Default constructor creates a null change object
*
* This object will have type() == LobChange::NoChange and calling apply() does
* nothing.
*/
LobChange ();
/** \brief Default copy operator, restricted to this class's fields
*/
LobChange ( const LobChange& other );
/** \brief Default assignment operator, restricted to this class's fields
*/
LobChange& operator= ( const LobChange& other );
/** \brief Compares two change objects by comparing all their fields
*
* All fields are compared with operator==() except data0 and data1, which are compared
* with Lob::equivalentTo().
*/
bool operator== ( const LobChange& other ) const;
/** \brief The tree in which the change will take place
*
* A change is described/created to take place within a certain Lob tree.
* Although it doesn't need to be applied to that tree, that is the default,
* and the LobChange object remembers that context. You can get that context
* by calling this function.
*
* The address to the specific location within the context() tree at which this change
* takes place (assuming it is an atomic change) can be retrieved using address(),
* and the Lob at that address can be retrieved with result(),
* assuming such a Lob exists; it may not, depending on whether this change is an
* insertion or removal, whether or not the change has been applied, etc.
*/
Lob context () const;
/** \brief The actual Lob that results from this change
*
* While newData() only applies to certain changes, and even in those cases it returns
* a copy of the data, this function gives the actual Lob that was modified, in its
* current state. It is equivalent to calling context().index( address() ).
*
* Note that if the change was of type Remove, RemovePair, or Compound, this will
* return the empty Lob; to see why, read the documentation for address().
*/
Lob result () const;
/** \brief The type of change this object represents
*
* It will be one of the choices defined in the LobChange::Type enum.
* \ref LobChangeTypes "See the LobChange class description for details on types."
*/
Type type () const;
/** \brief The address within context() to which this change applies
*
* The result of this function is based on this object's type().
* <table>
* <tr><th>type()</th><th>Result</th></tr>
* <tr><td>Insert</td>
* <td>the address the inserted Lob will have
* (which may be invalid before applying the change)</td></tr>
* <tr><td>InsertPair</td>
* <td>the address the inserted value from the pair will have
* (which may be invalid before applying the change)</td></tr>
* <tr><td>Remove</td>
* <td>the address of the Lob to remove has/had before its removal
* (which may be invalid after applying the change)</td></tr>
* <tr><td>RemovePair</td>
* <td>the address of the value from the pair to remove has/had before its removal
* (which may be invalid after applying the change)</td></tr>
* <tr><td>Change</td>
* <td>the address of the subtree that will be changed</td></tr>
* <tr><td>NoChange</td>
* <td>the empty address, because it does not matter</td></tr>
* <tr><td>Compound</td>
* <td>the empty address, but the addresses of individual steps
* can be queried; each may be different than the others</td></tr>
* </table>
*/
LobAddress address () const;
/** \brief The new data introduced by this change, if any
*
* The result of this function is based on this object's type().
* <table>
* <tr><th>type()</th><th>Result</th></tr>
* <tr><td>Insert</td><td>the new Lob the change inserts</td></tr>
* <tr><td>InsertPair</td><td>the value from the pair the change inserts</td></tr>
* <tr><td>Change</td><td>the value to which the Lob is changed</td></tr>
* <tr><td>any other type</td><td>an empty Lob</td></tr>
* </table>
*
* Note that this is only a copy of the modified data, when it applies. See also
* result(), which is more useful in some situations.
*/
Lob newData () const;
/** \brief The old data removed by this change, if any
*
* The result of this function is based on this object's type().
* <table>
* <tr><th>type()</th><th>Result</th></tr>
* <tr><td>Remove</td><td>the Lob the change removes</td></tr>
* <tr><td>RemovePair</td><td>the value from the pair the change removes</td></tr>
* <tr><td>Change</td><td>the value from which the Lob is changed</td></tr>
* <tr><td>any other type</td><td>an empty Lob</td></tr>
* </table>
*/
Lob oldData () const;
/** \brief If the change involves a pair, this is the name of the pair's key
*
* The result of this function is based on this object's type().
* <table>
* <tr><th>type()</th><th>Result</th></tr>
* <tr><td>InsertPair</td><td>the name of the key in the pair to be inserted</td></tr>
* <tr><td>RemovePair</td><td>the name of the key in the pair to be removed</td></tr>
* <tr><td>any other type</td><td>an empty string</td></tr>
* </table>
*/
QString keyName () const;
/** \brief If the change involves a pair, this is the content dictionary of the pair's key
*
* The result of this function is based on this object's type().
* <table>
* <tr><th>type()</th><th>Result</th></tr>
* <tr><td>InsertPair</td>
* <td>the content dictionary of the key in the pair to be inserted</td></tr>
* <tr><td>RemovePair</td>
* <td>the content dictionary of the key in the pair to be removed</td></tr>
* <tr><td>any other type</td><td>an empty string</td></tr>
* </table>
*/
QString keyCD () const;
/** \brief If the change is a compound one, this is the number of atomic steps in it
*
* If type() == LobChange::Compound, then this change is comprised of zero or more
* atomic steps, and this is how many there are. Use step() to query them. If type()
* is any other value, this returns zero.
*/
unsigned int count () const;
/** \brief Retrieves one of the steps in a compound change
*
* If this object has type() == LobChange::Compound, then it is comprised of zero or
* more atomic steps.
*
* Pass this routine any \a n between zero and count()-1 and it
* will return the corresponding step. The steps are to be interpreted as if step 0
* were the first step the change would execute (under apply()) and step count()-1
* the last. If \a n is too large, a newly constructed LobChange is returned (the
* null action, of type LobChange::NoChange).
*
* Each step is guaranteed to be atomic, so for no \a n should you find
* that step(n).type() == LobChange::Compound. That is, this is a flat structure,
* not a hierarchy.
*/
LobChange step ( unsigned int n ) const;
/** \brief Perform the action in this change object on the given Lob
*
* If the \a toThis parameter is not given (and thus defaults to an empty Lob),
* it will first be replaced by context() before performing the change. Thus
* the simplest way to call this routine is with no parameters, and it will act
* on the Lob from which it was created/recorded. If \a toThis is provided, it is
* used as the context instead, so that the change affects toThis.index( address() ).
*
* If this is a compound object, all steps are executed in the context provided in
* \a toThis (which defaults to context() if not provided). That is, none of the
* contexts of the steps are inspected or used at all.
*
* In the execution of this routine, this object may change. For instance, applying
* a LobChange::Change type() object to a Lob will inspect the subtree that's about
* to be changed, and store it for later retrieval using oldData(). This may or may
* not result in a change to that internally stored value.
*
* \return True if the change took place successfully. False if it failed, because
* the \a toThis object (or context() if no \a toThis was given) did not have
* the appropriate structure; for instance, trying to add an attribute to a
* subtree that isn't there, or trying to add child #7 to a node with only
* 3 children. NoChange actions always succeed, and Compound changes succeed
* if and only if all parts did; if one fails, none after that are attempted.
*/
bool apply ( Lob toThis = Lob() );
/** \brief Creates a new LobChange that would undo this one, if applied after it
*
* Mathematically speaking, this creates a "right inverse," as in the following code.
* \code
* Lob backup = myLob.copy();
* A.apply( myLob );
* A.inverse().apply( myLob );
* // here myLob.equivalentTo( backup, true ) is true
* \endcode
* It does not create a "left inverse" in the sense that the two apply lines above
* could not necessarily be reversed and expect the result to be the same.
*
* In common computer parlance, A.inverse() is a change that could "undo" A, if
* performed immediately after it. The inverse of an Insert is a Remove, the inverse
* of an InsertPair is a RemovePair, and the inverse of a Change, Compound, or NoChange
* is of the same type as the original. In each case, the internal data may be
* different (e.g., oldData() may become newData(), etc., as needed).
*/
LobChange inverse () const;
/** \brief Combine this object with the given one to create a compound LobChange
*
* This LobChange may be atomic or compound, as may \a withThis, but the result
* is guaranteed to be of type() LobChange::Compound. It will be a flat list,
* so that all atomic steps are accessible through step(). That is, no step()
* will be of type() LobChange::Compound. The order is that all steps in this
* object are first, followed by all steps in \a withThis.
*
* The new LobChange objects returned has the same context() as this one, meaning
* that the context() of \a withThis, if different, was disregarded.
*/
LobChange combine ( const LobChange& withThis ) const;
/** \brief Convenient way to access the combine() function.
*
* That is, <code>change1 + change2</code> is the same as
* <code>change1.combine( change2 )</code>.
*/
LobChange operator+ ( const LobChange& other ) const;
/** \brief Watch for any changes to the given Lob, and combine them into this object
*
* This provides a way of recording any kind of changes to Lobs for later undoing
* or playback. Simply call <code>myLobChange.startRecording( myLob );</code>, make any
* changes, and then call <code>myLobChanges.stopRecording();</code>. To undo the
* changes, call <code>myLobChanges.inverse().apply();</code>. To redo them, call
* <code>myLobChanges.apply();</code>.
*
* Calling this function sets the context() of this LobChange object to \a target.
* You can't be recording changes from more than one Lob at a time.
* Calling startRecording() twice without an intervening call to stopRecording()
* generates a silent stopRecording() call before the second start.
*/
void startRecording ( Lob target );
/** \brief Turn off recording changes to the given Lob
*
* This function is explained in the documentation for startRecording().
*/
void stopRecording ();
/** \brief Whether this change object is recording changes to a Lob
*
* Returns true if and only if the most recent call to startRecording() has not been
* followed up (yet) by a call to stopRecording().
*/
bool isRecording ();
/** For use in debugging
*/
QString toString ( bool verbose = false ) const;
public slots:
/** For use when recording changes
*/
void recordStep ( const LobChange& step );
private:
#ifdef LURCH_UNIT_TEST
friend class LURCH_UNIT_TEST;
#endif
// so that Lob can set up these objects however it sees fit, when emitting change signals
friend class Lob;
/** The tree (context) in which this LobChange object was recorded/created
*/
Lob root;
/** The location within the root (context) at which this change takes place
*/
LobAddress addr;
/** The type of this change.
* \ref LobChangeTypes "See the LobChange class description for details on types."
*/
Type ty;
/** Where any Lob data about the initial state (before the change) is stored.
*/
Lob data0;
/** Where any Lob data about the final state (after the change) is stored.
*/
Lob data1;
/** If the change involves an attribute key, this is its name.
*/
QString name;
/** If the change involves an attribute key, this is its content dictionary.
*/
QString cd;
/** If this change is a compound change, the atomic steps are stored in this list.
*/
QList<LobChange> steps;
/** Stores whether or not recording is in progress
*/
bool recordingInProgress;
/** Create a new LobChange object of Insert type, with all required data
*/
static LobChange newInsert ( Lob data );
/** Create a new LobChange object of InsertPair type, with all required data
*/
static LobChange newInsertPair ( QString keyName, QString keyCD, Lob value );
/** Create a new LobChange object of Remove type, with all required data
*/
static LobChange newRemove ( Lob data );
/** Create a new LobChange object of RemovePair type, with all required data
*/
static LobChange newRemovePair ( QString keyName, QString keyCD, Lob value );
/** Create a new LobChange object of Change type, with all required data
*/
static LobChange newChange ( Lob before, Lob after );
};
#endif // LOB_CHANGE