<@doc alias="recorder" hierarchy="GMLDOM"> The ModelRecorder is an object used for recording model changes (transactions) and enabling undo/redo functionality Instances of this class are created automatically by the system, and should not be created directly. To access the ModelRecorder object of the currently open model use the $ENV.recorder property. (c) SAP AG 2003-2006. All rights reserved. #INCLUDE[dev:defs.inc] /////////////////////////////////////////////////////////////////////// // CLASS HEADER Class ModelRecorder inherit Object; <@doc hide="Y"> Constructs and initializes a new transactions ModelRecorder object The model recorded by this object constructor(model) this.model = model; this.past = []; this.future = []; this.present = null; end /////////////////////////////////////////////////////////////////////// // PROPERTIES <@doc scope="private">Indicates whether the recorder is engaged in an undo or redo readonly property engaged = false; <@doc scope="private">Gets the model recorded by this object readonly property model = ^gml:Model; <@doc scope="private">Gets the past recordings buffer readonly property past = null; <@doc scope="private">Gets the current recording readonly property present = null; <@doc scope="private">Gets the future recordings buffer readonly property future = null; <@doc scope="private">Gets the recorder's transactions nesting level readonly property level = 0; <@doc scope="private">Gets a stamp value that can be used to monitor recorder state changes readonly property stamp = 0; ///////////////////////////////////////////////////////////////////// // METHODS <@doc> Indicates whether the recorder is at the beginning (i.e., there is nothing to undo) Returns the test result method bof() return (this.past.length == 0); end <@doc> Indicates whether the recorder is at the end (i.e., there is nothing to redo) Returns the test result method eof() return (this.future.length == 0); end <@doc> Executes a new transaction and appends it to the current recording. The transaction type Id (see @env:KitTransactions!TRANSACTION_TYPE) A variable-length arguments list to pass to the transaction constructor The transaction object, or null in case of any error If there is no current recording, a new recording containing the given transaction will be automatically created. method execute(type, p1, p2, p3, p4, p5, p6, p7) try { var tstor = $ENV.getTransType(type).trans; var trans = new tstor(p1, p2, p3, p4, p5, p6, p7); } catch(e) { var msg1 = '#TEXT[XMSG_FAILED_TO_EXECUTE]'; var msg2 = 'transaction'; #LOG[4, msg1 + ' ' +type+ ' ' + msg2] #TRACE[4, e.description] return null; } if (!this.engaged) { if (this.present) { // append the transaction to the current recording this.present.push(trans); } else { // automatically begin a new recording, append the transaction, and commit this.begin(type); this.present.push(trans); this.commit(); } } return (trans.phase & #[TRANS_DO]) ? trans : null; end <@doc> Begins a new recording A display name to assign to the recording. An object representing a snapshot of the current window at its last stable state prior to the recording (see @dev:Workspace!captureSnapshot). If omitted, the snapshot of the window at the exact moment that the recording has began will be used. A transactions recording is a sequence of one or more transactions that are undone or redone in a single scope. Once a recording has been started, you can use the ~add method to add new transactions to it. As soon as you are done with the actions that constitute your scope you should call the ~commit resp. ~rollback method to accept resp. cancel the recording. You must balance calls to the ~begin method with calls to the ~commit method. Nested calls to the ~begin method are merged, so at most a single recording is active at any time. method begin(name, snapshot) if (this.engaged) return; if (!this.present) { this.present = []; this.present.name = name || ''; this.present.beforeshot = snapshot || $WIN.captureSnapshot(); } this.level++; end <@doc> Accepts the current recording An object representing a snapshot of the current window at its next stable state after the recording. If omitted, the snapshot taken at the beginning of the recording will be used. method commit(snapshot) // validate present record if (!this.present || this.engaged) return; this.level--; if (this.level > 0) return; var record = this.present; this.present = null; this.level = 0; if (record.length == 0) return; record.aftershot = snapshot || record.beforeshot || null; // push present record to the past, and clear the future var len1=this.past.length, len2=this.future.length; this.past.push(record); if (len2 > 0) this.future.splice(0, len2); if (len1 == 0 || len2 > 0) $WIN.adjustState(this.getState(), #[WIN_UNDO|WIN_REDO]); this.stamp++; end <@doc> Cancels the current recording method rollback() if (!this.present || this.engaged) return; #LOG[1, "ROLLBACK was called, please view the trace for more details" ]; // undo the present record var record = this.present; var msg = []; msg.push("ROLLBACK has been executed, The following entries have been rollbacked(in the order of execution):\n"); for (var i=record.length-1; i>=0; i--) { try { msg.push('Entry ' + i +' - ' + record[i].type+':\n'+this.entryToString(record[i])); record[i].undo(); } catch (e) { #TRACE[4, "Error in rollback: " + e.description]; } } #TRACE[1, JOIN(msg) ]; this.present = null; this.level = 0; // if nothing left to undo, clear dirty flag if (this.past.length == 0) this.model.clearDirty(); // restore the window to the snapshot captured before the transaction was executed if (record.beforeshot) $WIN.restoreSnapshot(record.beforeshot); end <@doc> Reverses the last recording in the past, if any method undo() if (this.present || this.past.length == 0 || this.engaged) return; this.engaged = true; // peek record and test if context switch is needed var record = this.past[this.past.length-1]; var snapshot = record.beforeshot; var unit = $ENV.contextUnit; if (snapshot && (!unit || unit.id != snapshot.unit_id)) { $WIN.restoreSnapshot(snapshot); this.engaged = false; return; } // pop record from the past and push it to the future var len1=this.past.length, len2=this.future.length; this.future.push(this.past.pop()); if (len1 == 1 || len2 == 0) $WIN.adjustState(this.getState(), #[WIN_UNDO|WIN_REDO]); // undo the popped record for (var i=record.length-1; i>=0; i--) { try { record[i].undo(); } catch (e) { #TRACE[4, "Error in undo: " + e.description]; } } // if nothing left to undo, clear dirty flag if (this.past.length == 0) this.model.clearDirty(); // restore the window to the snapshot captured before the transaction was executed if (snapshot) $WIN.restoreSnapshot(snapshot); this.stamp++; this.engaged = false; end <@doc> Reapplies the first recording in the future, if any method redo() if (this.present || this.future.length == 0 || this.engaged) return; this.engaged = true; // peek record and test if context switch is needed var record = this.future[this.future.length-1]; var snapshot = record.aftershot; var unit = $ENV.contextUnit; if (snapshot && (!unit || unit.id != snapshot.unit_id)) { $WIN.restoreSnapshot(snapshot); this.engaged = false; return; } // pop record from the future and push it to the past var len1=this.past.length, len2=this.future.length; this.past.push(this.future.pop()); if (len1 == 0 || len2 == 1) $WIN.adjustState(this.getState(), #[WIN_UNDO|WIN_REDO]); // redo the popped record for (var i=0, len=record.length; i Gets the recorder state The recorder state method getState() return (this.past.length==0 ? 0 : #[WIN_UNDO]) + (this.future.length==0 ? 0 : #[WIN_REDO]); end <@doc"> Gets the recorder stamp The recorder stamp method getStamp() return this.stamp; end <@doc> Gets the recorder busy state. ~true if the recorder is in the middle of an undo, redo or rollback operation, ~false otherwise. method getIsBusy() return this.engaged; end <@doc scope="private"> Gets an entry as a string A string representation of the entry method entryToString(entry) if (!entry) return null; var str = []; for (var key in entry) { if (!entry.hasOwnProperty(key)) continue; var value=entry[key]; switch (typeof value) { case 'function': value='function'; break; case 'string': value='"'+value+'"'; break; } str.push( '\t'+key+(key.length > 8 ? ':\t' : ':\t\t')+value+'\n'); } return JOIN(str); end