This script allows you to view object and module history across all baselines. The dialog displays history in listviews on three tabs, showing complete object, module and session history.
The object tab displays history for the current object. You can browse through objects using the Prev and Next buttons or select the object directly by selecting it in the module window. The module tab displays all module history as well as object deletions. The sessions tab displays the history of all editing sessions and baseline creations. All listviews can be sorted by any column.
This script demonstrates some very strange vaguaries of the history system such as the fact that module level attribute changes are recorded as modifyObject events, but with the object absolute number being zero! (see lines 924 and 971). Take a look at line 1009 to see how session numbers are handled.
For those of you that are interested, this script is a good example of how to use the DxlObject type together with skip lists to build practical data structures.
// Smart History Viewer - Standalone version. /* S M A R T D X L L I M I T E D Description: Provides a dialog to view history accross multiple baselines. Save this file, and tophat.bmp to <DOORS ADDINS>/SmartDXL Author: Tony Goodman tony@smartdxl.com Change History: 25-NOV-2008 Tony Goodman Initial version. 24-FEB-2011 Tony Goodman Locate bitmap from addins path. Add banner title and website url. Use more complex names for global variables. 17-May-2011 Tony Goodman Standalone version. Single DXL file for easy installation (With the exception of the optional logo bitmap file) */ pragma runLim, 0 /**************************** Smart DXL logo bitmap file - this is loaded once when first dialog is displayed. ****************************/ Bitmap smartDxlLogo = null string SMART_DXL_DB_TITLE = "Smart DXL" string SMART_DXL_BANNER_WEBSITE = "www.smartdxl.com" string SMART_DXL_BANNER_BITMAP = "SmartDXL\\tophat.bmp" DBE smartDxlBanner = null bool smartDxlLogoFound = false string smartDxlBannerText = "" int smartDxlLogoWidth = 0 int smartDxlLogoHeight = 0 /**************************** getAbsolutePath ****************************/ string getAbsolutePath(string sFileName) { string sResult = "" string sAddinsPathList = (getenv "ADDINS") ";" string sDoorsAddinsPathList = (getenv "DOORSADDINS") ";" if (sAddinsPathList != sDoorsAddinsPathList) { sAddinsPathList = sDoorsAddinsPathList ";" sAddinsPathList } Regexp rPath = regexp "([^;]*);(.*)" string sLeft = "" string sRight = "" if (rPath sAddinsPathList) { // test each path in the semicolon separated list while (rPath sAddinsPathList) { sLeft = sAddinsPathList[match 1] sRight = sAddinsPathList[match 2] if (fileExists_ (sLeft "\\" sFileName)) { sResult = sLeft "\\" sFileName break; } else { sAddinsPathList = sRight } } } return(sResult) } /***************************** smartDrawLogo *****************************/ void smartDrawLogo(DBE cnvs) { string smartDxlBitmapFile = "" Stat st = null if (null smartDxlLogo) { // bitmap has not been loaded yet, so load it smartDxlBitmapFile = getAbsolutePath(SMART_DXL_BANNER_BITMAP) // check that the file exists st = create(smartDxlBitmapFile) if (!null st) { // file does exist so we can load the bitmap delete(st) smartDxlLogo = loadBitmap(cnvs, smartDxlBitmapFile, false, smartDxlLogoWidth, smartDxlLogoHeight) } } // draw the bitmap if it was successfully loaded if (!null smartDxlLogo) { drawBitmap(cnvs, smartDxlLogo, 0, 0) smartDxlLogoFound = true } } /**************************** doLogoMouse ****************************/ void doLogoMouse(DBE cnv, int but, bool ctrl, int x, int y) { if (x > width(cnv) - width(cnv, SMART_DXL_BANNER_WEBSITE) - 5) { activateURL(SMART_DXL_BANNER_WEBSITE) } } /**************************** doRepaintSmartDxlBanner ****************************/ void doRepaintSmartDxlBanner(DBE cnv) { int canvasWidth = width(cnv) int titleHeight = 0 int y = 0 realBackground(cnv, realColor_White) smartDrawLogo(cnv) realColor(cnv, realColor_Black) font(cnv, 4, HeadingsFont) y = (height(cnv) / 2) + (height(cnv, smartDxlBannerText) / 2) - 3 draw(cnv, smartDxlLogoWidth + 3, y, smartDxlBannerText) realColor(cnv, realColor_Blue) font(cnv, 9, HeadingsFont) draw(cnv, canvasWidth - width(cnv, SMART_DXL_BANNER_WEBSITE) - 5, y - 1, SMART_DXL_BANNER_WEBSITE) line(cnv, canvasWidth - width(cnv, SMART_DXL_BANNER_WEBSITE) - 5, y, canvasWidth - 5, y) } /**************************** addSmartDxlBanner ****************************/ void addSmartDxlBanner(DB db) { smartDxlBanner = canvas(db, 0, 32, doRepaintSmartDxlBanner) set(smartDxlBanner, doLogoMouse) } /**************************** showHelp ****************************/ void showHelp(string helpFile) { system("\"C:\\Program Files\\Internet Explorer\\iexplore.exe\" -nohome \"" (getAbsolutePath(helpFile)) "\"") } /**************************** smartDialog ****************************/ DB smartDialog(string banner, int options) { DB smartDb = null smartDb = create(SMART_DXL_DB_TITLE, options) smartDxlBannerText = banner addSmartDxlBanner(smartDb) return(smartDb) } /**************************** smartDialog ****************************/ DB smartDialog(string dbTitle) { return(smartDialog(dbTitle, styleCentered|styleFloating)) } /**************************** smartDialog ****************************/ DB smartDialog(Module mParent, string banner, int options) { DB smartDb = null smartDb = create(mParent, SMART_DXL_DB_TITLE, options) smartDxlBannerText = banner addSmartDxlBanner(smartDb) return(smartDb) } /**************************** smartDialog ****************************/ DB smartDialog(Module mParent, string dbTitle) { return(smartDialog(mParent, dbTitle, styleCentered|styleFloating)) } /**************************** smartDialog ****************************/ DB smartDialog(DB dbParent, string banner, int options) { DB smartDb = null smartDb = create(dbParent, SMART_DXL_DB_TITLE, options) smartDxlBannerText = banner addSmartDxlBanner(smartDb) return(smartDb) } /**************************** smartDialog ****************************/ DB smartDialog(DB dbParent, string banner) { return(smartDialog(dbParent, banner, styleCentered|styleFloating)) } // Sort Callbacks for listViews /**************************** doSortString ****************************/ int doSortString(string s1, string s2) { return(cistrcmp(s1, s2)) } /**************************** doSortDate ****************************/ int doSortDate(string s1, string s2) { Date d1 = "1 January 1970" Date d2 = "1 January 1970" if (s1 != "") { d1 = date(s1) } if (s2 != "") { d2 = date(s2) } if (d1 > d2) { return(-1) } else if (d2 > d1) { return(1) } return(0) } /**************************** doSortInteger ****************************/ int doSortInteger(string s1, string s2) { int i1 = intOf(s1) int i2 = intOf(s2) if (i1 > i2) { return(1) } else if (i2 > i1) { return(-1) } return(0) } /**************************** doSortLegal ****************************/ int doSortLegal(string s1, string s2) { int i1 = 0 int i2 = 0 i1 = intOf(s1) i2 = intOf(s2) if (i1 > i2) { return(1) } else if (i2 > i1) { return(-1) } else { if (length(s1) > length(i1 "")) { if (length(s2) > length(i2 "")) { return(doSortLegal(s1[length(i1 "") + 1:], s2[length(i2 "") + 1:])) } return(1) } else if (length(s2) > length(i2 "")) { return(-1) } return(0) } return(0) } // columns in listview const int BS_NAME_COLUMN = 0 const int BS_CREATED_BY_COLUMN = 1 const int BS_CREATED_ON_COLUMN = 2 const int BS_DELETED_BY_COLUMN = 3 const int BS_DELETED_ON_COLUMN = 4 string bsListItems[] = {} DB bsMainDb = null DBE bsBaselinesDbe = null // list of all baselines found in the module Skip bsSelectedBaselinesSkip = null // list of baselines selected in the listview by the user Skip bsAllBaselinesSkip = null // true if the user selects one or more baselines. // false if no baselines are selected or the user clicks cancel bool bsBaselinesSelected = false /**************************** bsUpdateBaselineList Update the list view with entries for all baselines found in the given module. Also adds each baseline to the list of all baselines. This list is used later to get a handle on the selected baselines according to their position in the listview. ****************************/ void bsUpdateBaselineList(Module m) { int i = 0 Baseline b = null string strDate = "" Date baselineDate = null Date deletedDate = null // clear entries from the list view empty(bsBaselinesDbe) // loop through al baselines in the module for b in all m do { strDate = "" baselineDate = dateOf(b) if (!null baselineDate) { strDate = stringOf(baselineDate) } // add baseline to list of all baselines put(bsAllBaselinesSkip, i, b) // add an entry for this baseline to list view insert(bsBaselinesDbe, i, major(b) "." minor(b) " " suffix(b), iconNone) // set column values set(bsBaselinesDbe, i, BS_CREATED_BY_COLUMN, user(b)) set(bsBaselinesDbe, i, BS_CREATED_ON_COLUMN, strDate) if (deleted(b)) { strDate = "" deletedDate = deletedOn(b) if (!null deletedDate) { strDate = stringOf(deletedDate) } set(bsBaselinesDbe, i, BS_DELETED_BY_COLUMN, deletedBy(b)) set(bsBaselinesDbe, i, BS_DELETED_ON_COLUMN, strDate) } i++ } } /**************************** bsDoCancel Callback for Cancel button. ****************************/ void bsDoCancel(DB db) { bsBaselinesSelected = false release(bsMainDb) } /**************************** bsDoApply Callback for OK button. Gets selections from the listview and updates the selected baseline list. ****************************/ void bsDoApply(DB db) { int i = 0 Baseline b = null bsBaselinesSelected = false // loop through entries in the list view for (i = 0; i < noElems(bsBaselinesDbe); i++) { // if the entry is checked (selected) ... if (getCheck(bsBaselinesDbe, i)) { bsBaselinesSelected = true // look up the corresponding baseline if (find(bsAllBaselinesSkip, i, b)) { // add to selected baseline list put(bsSelectedBaselinesSkip, i, b) } } } if (!bsBaselinesSelected) { infoBox("No baselines selected!") return } release(bsMainDb) } /**************************** bsDoSelectAll Callback for the Select All button. Sets all the entries in the list to checked. ****************************/ void bsDoSelectAll(DB db) { int i = 0 for (i = 0; i < noElems(bsBaselinesDbe); i++) { setCheck(bsBaselinesDbe, i, true) } } /**************************** bsDoDeselectAll Callback for the Deselect All button. Sets all the entries in the list to unchecked. ****************************/ void bsDoDeselectAll(DB db) { int i = 0 for (i = 0; i < noElems(bsBaselinesDbe); i++) { setCheck(bsBaselinesDbe, i, false) } } /**************************** bsGetUserSelectedBaselines Launches a dialog displaying a list of baselines for the specified module. The user is able to select multiple baselines from the list. The selected baselines are returned in the skip list, indexed by an integer starting at zero. If selectAll is TRUE then the all entries in the list are initially checked. Returns TRUE if the user selects on or more baselines, otherwise FALSE. ****************************/ bool bsGetUserSelectedBaselines(Module m, Skip skip, string dbTitle, string msg, bool selectAll) { Baseline b = null bsSelectedBaselinesSkip = create bsAllBaselinesSkip = create bsMainDb = smartDialog(m, dbTitle, styleCentered) label(bsMainDb, msg) bsBaselinesDbe = listView(bsMainDb, listViewOptionCheckboxes | listViewOptionSortText, 455, 10, bsListItems) close(bsMainDb, false, bsDoCancel) apply(bsMainDb, "Select All", bsDoSelectAll) apply(bsMainDb, "Deselect All", bsDoDeselectAll) apply(bsMainDb, bsDoApply) ok(bsMainDb, "Cancel", bsDoCancel) realize(bsMainDb) insertColumn(bsBaselinesDbe, BS_NAME_COLUMN, "Baseline", 80, iconNone) insertColumn(bsBaselinesDbe, BS_CREATED_BY_COLUMN, "Created By", 80, iconNone) insertColumn(bsBaselinesDbe, BS_CREATED_ON_COLUMN, "Created On", 105, iconNone) insertColumn(bsBaselinesDbe, BS_DELETED_BY_COLUMN, "Deleted By", 80, iconNone) insertColumn(bsBaselinesDbe, BS_DELETED_ON_COLUMN, "Deleted On", 105, iconNone) set(bsBaselinesDbe, BS_NAME_COLUMN, doSortLegal) set(bsBaselinesDbe, BS_CREATED_BY_COLUMN, doSortString) set(bsBaselinesDbe, BS_CREATED_ON_COLUMN, doSortDate) set(bsBaselinesDbe, BS_DELETED_BY_COLUMN, doSortString) set(bsBaselinesDbe, BS_DELETED_ON_COLUMN, doSortDate) bsUpdateBaselineList(m) if (selectAll) { bsDoSelectAll(bsMainDb) } block(bsMainDb) hide(bsMainDb) destroy(bsMainDb) bsMainDb = null for b in bsSelectedBaselinesSkip do { put(skip, (int key bsSelectedBaselinesSkip), b) } delete(bsSelectedBaselinesSkip) delete(bsAllBaselinesSkip) return(bsBaselinesSelected) } /* Data structures and utilities for handling history records. History types: const HistoryType unknown const HistoryType createType const HistoryType modifyType const HistoryType deleteType const HistoryType createAttr const HistoryType modifyAttr const HistoryType deleteAttr const HistoryType createObject const HistoryType copyObject const HistoryType modifyObject const HistoryType deleteObject const HistoryType unDeleteObject const HistoryType purgeObject const HistoryType moveObject const HistoryType clipCutObject const HistoryType clipMoveObject const HistoryType clipCopyObject const HistoryType createModule const HistoryType baselineModule const HistoryType partitionModule const HistoryType acceptModule const HistoryType returnModule const HistoryType rejoinModule const HistoryType createLink const HistoryType modifyLink const HistoryType deleteLink const HistoryType insertOLE const HistoryType removeOLE const HistoryType changeOLE const HistoryType pasteOLE const HistoryType cutOLE const HistoryType readLocked const HistoryType commentObject const HistoryType commentModule */ // indices to DxlObject structure const string HIST_AUTHOR = "author" const string HIST_DATE = "date" const string HIST_BASELINE = "baseline" const string HIST_SESSION = "session" const string HIST_DESCRIPTION = "description" const string HIST_OLD_VALUE = "oldvalue" const string HIST_NEW_VALUE = "newvalue" const string HIST_TYPE = "type" const string HIST_LOCKED = "locked" // data structure for object history Skip skipObjects = create Skip skipNumRecords = create // data structure for module history Skip skipModuleHistory = create int numModuleRecords = 0 // data structure for session history Skip skipSessionHistory = create int numSessionRecords = 0 Module currMod = null Object currObj = null int currObjNumber = 0 /**************************** printObjectHistory Use for degug only. Prints all data structures. ****************************/ void printObjectHistory() { Object o = null Skip skipHistory = null DxlObject hist = null HistoryType historyType = null int objNo = 0 int rec = 0 for skipHistory in skipObjects do { // get object number objNo = (int key skipObjects) // lookup number of history records stored for this object find(skipNumRecords, objNo, rec) print("Object " (int key skipObjects) " has " rec " history records\n") for hist in skipHistory do { print("\tHistory record " (int key skipHistory) "\n") if (!(bool hist->HIST_LOCKED)) { print("\t\tDate: " (Date hist->HIST_DATE) "\n") print("\t\tAuthor: " (string hist->HIST_AUTHOR) "\n") print("\t\tSession: " (int hist->HIST_SESSION) "\n") print("\t\tBaseline: " (string hist->HIST_BASELINE) "\n") historyType = (HistoryType hist->HIST_TYPE) print("\t\tDescription: " (string hist->HIST_DESCRIPTION) "\n") print("\t\t\tFrom: " (string hist->HIST_OLD_VALUE) "\n") print("\t\t\tTo: " (string hist->HIST_NEW_VALUE) "\n") } else { print("Read Locked Data\n") } } return } } /**************************** isObjectOperation ****************************/ bool isObjectOperation(HistoryType historyType) { return((historyType == createObject) || (historyType == copyObject) || (historyType == deleteObject) || (historyType == unDeleteObject) || (historyType == purgeObject) || (historyType == clipCutObject) || (historyType == clipCopyObject) || (historyType == createLink) || (historyType == modifyLink) || (historyType == deleteLink) || (historyType == insertOLE) || (historyType == removeOLE) || (historyType == changeOLE) || (historyType == pasteOLE)) } /**************************** isModuleOperation This function deliberately includes the deleteObject and purgeObject operations as these do not appear in the object history. modifyObject is required for changes to module level attributes. ****************************/ bool isModuleOperation(HistoryType historyType) { return(historyType == createModule || historyType == baselineModule || historyType == partitionModule || historyType == acceptModule || historyType == rejoinModule || historyType == returnModule || historyType == commentModule || historyType == createAttr || historyType == modifyAttr || historyType == deleteAttr || historyType == createType || historyType == modifyType || historyType == deleteType || historyType == deleteObject || historyType == purgeObject || historyType == modifyObject) } /**************************** getObjectHistory ****************************/ void getObjectHistory(Module m, string currBaseline) { Object o = null int objNo = 0 int rec = 0 bool locked = false History h = null HistoryType historyType = null // loop through all objects in the module an extract object history for o in entire(m) do { objNo = o."Absolute Number" // skip list to hold history records for the object Skip skipHistory = null // see if the history list already exists, if not then create it if (find(skipObjects, objNo, skipHistory)) { //print("skipHistory found for object " objNo "\n") // skip list exists - find next index to use if (find(skipNumRecords, objNo, rec)) { //print("numrecords found = " rec "\n") } //rec++ } else { skipHistory = create // new skip list, so first entry will be indexed at zero rec = 0 } // loop through history records for the object for h in o do { // create structure to hold details of the history record DxlObject hist = new // history record may be read-locked locked = h.readlocked hist->HIST_LOCKED = locked // record the type historyType = h.type hist->HIST_TYPE = historyType // if it is not locked we can read the details if (!locked) { hist->HIST_DATE = h.date hist->HIST_AUTHOR = h.author hist->HIST_SESSION = h.sessionNo hist->HIST_BASELINE = currBaseline //hist->HIST_DETAILS = "<no further details>" if (historyType == modifyObject) { // object modification has old and new values hist->HIST_DESCRIPTION = "Modify Attribute: " h.attrName // TBD use buffers to hold old and new values. hist->HIST_OLD_VALUE = h.oldValue hist->HIST_NEW_VALUE = h.newValue } else if (historyType == moveObject or historyType == clipMoveObject) { // object was moved or copied and pasted hist->HIST_DESCRIPTION = "Move object from " h.position " to " h.newPosition "" } else if (historyType == clipCopyObject) { // object was created by copying another hist->HIST_DESCRIPTION = "Copy object from " h.oldAbsNo " to " h.absNo "" } else if (historyType == commentObject) { hist->HIST_DESCRIPTION = "Comment" hist->HIST_NEW_VALUE = h.newValue } else if (isObjectOperation(historyType)) { hist->HIST_DESCRIPTION = goodStringOf(historyType) } else { hist->HIST_DESCRIPTION = "<unknown history type>" } } // add history structure to list of history records for this object put(skipHistory, rec++, hist) } // store the number of history records in the list delete(skipNumRecords, objNo) put(skipNumRecords, objNo, rec) // add list of history records for this object to the object list delete(skipObjects, objNo) put(skipObjects, objNo, skipHistory) } } /**************************** getModuleHistory ****************************/ void getModuleHistory(Module m, string currBaseline) { int rec = 0 bool locked = false string modification = "" History h = null HistoryType historyType = null // loop through history records (this loops through ALL history // because we want the object delete and purge history records // in addition to the module specific history records. for h in m do { historyType = h.type if (!isModuleOperation(historyType)) { continue } // filter out modifyObject records that are not for module level attributes if (historyType == modifyObject && h.absNo != 0) { continue } // create structure to hold details of the history record DxlObject hist = new // record the type hist->HIST_TYPE = historyType // history record may be read-locked locked = h.readlocked hist->HIST_LOCKED = locked // if it is not locked we can read the details if (!locked) { hist->HIST_DATE = h.date hist->HIST_AUTHOR = h.author hist->HIST_SESSION = h.sessionNo hist->HIST_BASELINE = currBaseline //hist->HIST_DETAILS = "<no further details>" modification = goodStringOf(historyType) hist->HIST_DESCRIPTION = modification if (historyType == createAttr || historyType == modifyAttr || historyType == deleteAttr) { // attribute modification hist->HIST_DESCRIPTION = modification ": " h.attrName } else if (historyType == createType || historyType == modifyType || historyType == deleteType) { // attribute type modification hist->HIST_DESCRIPTION = modification ": " h.typeName } else if (historyType == commentModule) { hist->HIST_DESCRIPTION = "Comment" hist->HIST_NEW_VALUE = h.newValue } else if (historyType == deleteObject || historyType == purgeObject) { hist->HIST_DESCRIPTION = modification ": " h.absNo "" } else if (historyType == modifyObject && (h.absNo == 0)) { // it seems that modifications to module level attributes are stored // as modifyObject history records with an absolute number of zero. hist->HIST_DESCRIPTION = "Modify Attribute: " h.attrName // TBD use buffers to hold old and new values. hist->HIST_OLD_VALUE = h.oldValue hist->HIST_NEW_VALUE = h.newValue } } // store history structure in skip list put(skipModuleHistory, numModuleRecords++, hist) } } /**************************** getSessionHistory ****************************/ void getSessionHistory(Module m) { HistorySession hs = null string sBaseline = "" for hs in m do { // create structure to hold details of the history record DxlObject hist = new hist->HIST_DATE = when(hs) hist->HIST_AUTHOR = who(hs) // Sessions are 0-based in the core; the 1+ here is to account for the fact that // number(s) uses this 0-based scheme whereas sessionNo does // this 1+ internally. hist->HIST_SESSION = number(hs) + 1 sBaseline = baseline(hs) if (!null sBaseline) { hist->HIST_BASELINE = sBaseline hist->HIST_DESCRIPTION = "Created baseline " sBaseline } //hist->HIST_DETAILS = "<no details>" put(skipSessionHistory, numSessionRecords++, hist) } } /**************************** getHistory ****************************/ void getHistory(Module m) { string currBaseline = "" string s = "" Baseline b = null // extract baseline information if (isBaseline(m)) { b = baselineInfo(m) // only display the suffix if is is not empty s = (suffix b) "" if (s != "") { s = " (" s ")" } currBaseline = (major b) "." (minor b) "" s } else { currBaseline = "current" } // get object history records getObjectHistory(m, currBaseline) // get module history records getModuleHistory(m, currBaseline) } // listview column indices const int HIST_AUTHOR_COLUMN = 0 const int HIST_SESSION_COLUMN = 1 const int HIST_DATE_COLUMN = 2 const int HIST_BASELINE_COLUMN = 3 const int HIST_DESCRIPTION_COLUMN = 4 const int HIST_RECORD_COLUMN = 5 // tab indices const int TAB_OBJECT = 0 const int TAB_MODULE = 1 const int TAB_SESSIONS = 2 int selectedTab = TAB_OBJECT // main dialog DB dbMain = null DBE dbeTab = null // elements DBE dbeLabel = null DBE dbeListView = null DBE dbeDetailsFrame = null DBE dbeOldValue = null DBE dbeNewValue = null DBE btnPrev = null DBE btnNext = null string columnTitles[] = {} string tabLabels[] = { "Object", "Module", "Sessions" } string details = "" //Trigger g_mTrigger = null Trigger preSyncTrigger = null Trigger postSyncTrigger = null Skip skipOldValues = null Skip skipNewValues = null /**************************** clearDetailSkips ****************************/ void clearDetailSkips() { // dynamically store the old and new values in a skip list, referenced by // history record number. this allows us to look up the values for display // when the user selects an entry in the listview. if (!null skipOldValues) { delete(skipOldValues) } skipOldValues = create if (!null skipNewValues) { delete(skipNewValues) } skipNewValues = create } /**************************** showObjectHistory Update the dialog with information read from the data structures. ****************************/ void showObjectHistory() { Skip skipHistory = null DxlObject hist = null HistoryType historyType = null int objNo = 0 int rec = 0 objNo = currObj."Absolute Number" clearDetailSkips() // check that there is an entry for this object in the list if (find(skipObjects, objNo, skipHistory) && find(skipNumRecords, objNo, rec)) { if (rec > 0) { // there are history records for this object for hist in skipHistory do { rec = noElems(dbeListView) // insert a row in the list view insert(dbeListView, rec, "", iconNone) historyType = (HistoryType hist->HIST_TYPE) if (!(bool hist->HIST_LOCKED)) { set(dbeListView, rec, HIST_AUTHOR_COLUMN, (string hist->HIST_AUTHOR)) set(dbeListView, rec, HIST_SESSION_COLUMN, (int hist->HIST_SESSION) "") set(dbeListView, rec, HIST_DATE_COLUMN, stringOf(Date hist->HIST_DATE) "") set(dbeListView, rec, HIST_BASELINE_COLUMN, (string hist->HIST_BASELINE)) set(dbeListView, rec, HIST_DESCRIPTION_COLUMN, (string hist->HIST_DESCRIPTION)) // used as an index to the details lists set(dbeListView, rec, HIST_RECORD_COLUMN, rec "") // store details in lists - these can then be retrieved when user selects // an entry in the listview put(skipOldValues, rec, (string hist->HIST_OLD_VALUE)) put(skipNewValues, rec, (string hist->HIST_NEW_VALUE)) } else { set(dbeListView, rec, HIST_DESCRIPTION_COLUMN, goodStringOf(historyType) ": <Read Locked Data>") set(dbeListView, rec, HIST_AUTHOR_COLUMN, "<Read Locked Data>") set(dbeListView, rec, HIST_SESSION_COLUMN, "<Read Locked Data>") set(dbeListView, rec, HIST_DATE_COLUMN, "<Read Locked Data>") set(dbeListView, rec, HIST_BASELINE_COLUMN, "<Read Locked Data>") set(dbeListView, rec, HIST_RECORD_COLUMN, rec "") } } } else { // there are no history records for this object insert(dbeListView, 0, "", iconNone) set(dbeListView, 0, HIST_DATE_COLUMN, "<No history found>" "") } } else { // there is no entry for this object insert(dbeListView, 0, "", iconNone) set(dbeListView, 0, HIST_DESCRIPTION_COLUMN, "<Object not found>") } } /**************************** showModuleHistory Update the dialog with information read from the data structures. ****************************/ void showModuleHistory() { DxlObject hist = null HistoryType historyType = null int rec = 0 clearDetailSkips() if (numModuleRecords > 0) { // there are history records for this object for hist in skipModuleHistory do { rec = noElems(dbeListView) // insert a row in the list view insert(dbeListView, rec, "", iconNone) historyType = (HistoryType hist->HIST_TYPE) if (!(bool hist->HIST_LOCKED)) { set(dbeListView, rec, HIST_AUTHOR_COLUMN, (string hist->HIST_AUTHOR)) set(dbeListView, rec, HIST_SESSION_COLUMN, (int hist->HIST_SESSION) "") set(dbeListView, rec, HIST_DATE_COLUMN, stringOf(Date hist->HIST_DATE) "") set(dbeListView, rec, HIST_BASELINE_COLUMN, (string hist->HIST_BASELINE)) set(dbeListView, rec, HIST_DESCRIPTION_COLUMN, (string hist->HIST_DESCRIPTION)) set(dbeListView, rec, HIST_RECORD_COLUMN, rec "") // store details in lists - these can then be retrieved when user selects // an entry in the listview put(skipOldValues, rec, (string hist->HIST_OLD_VALUE)) put(skipNewValues, rec, (string hist->HIST_NEW_VALUE)) } else { set(dbeListView, rec, HIST_DESCRIPTION_COLUMN, goodStringOf(historyType) ": <Read Locked Data>") set(dbeListView, rec, HIST_AUTHOR_COLUMN, "<Read Locked Data>") set(dbeListView, rec, HIST_SESSION_COLUMN, "<Read Locked Data>") set(dbeListView, rec, HIST_DATE_COLUMN, "<Read Locked Data>") set(dbeListView, rec, HIST_BASELINE_COLUMN, "<Read Locked Data>") set(dbeListView, rec, HIST_RECORD_COLUMN, rec "") } } } else { // there are no history records for this object insert(dbeListView, 0, "", iconNone) set(dbeListView, 0, HIST_DATE_COLUMN, "<No history found>") } } /**************************** showSessionHistory Update the dialog with information read from the data structures. ****************************/ void showSessionHistory() { DxlObject hist = null int rec = 0 clearDetailSkips() if (numSessionRecords > 0) { // there are history records for this object for hist in skipSessionHistory do { rec = noElems(dbeListView) // insert a row in the list view insert(dbeListView, rec, "", iconNone) set(dbeListView, rec, HIST_AUTHOR_COLUMN, (string hist->HIST_AUTHOR)) set(dbeListView, rec, HIST_SESSION_COLUMN, (int hist->HIST_SESSION) "") set(dbeListView, rec, HIST_DATE_COLUMN, stringOf(Date hist->HIST_DATE) "") set(dbeListView, rec, HIST_BASELINE_COLUMN, (string hist->HIST_BASELINE)) set(dbeListView, rec, HIST_DESCRIPTION_COLUMN, (string hist->HIST_DESCRIPTION)) } } else { // there are no history records for this object insert(dbeListView, 0, "", iconNone) set(dbeListView, 0, HIST_DATE_COLUMN, "<No sessions found>") } } /**************************** getBaselineHistory ****************************/ bool getBaselineHistory(Skip skipBaselines) { Baseline thisBasline = null Module moduleBaseline = null int bCount = 0 int numBaselines = 0 bool autoShutdown = false // count baselines for progress for thisBasline in skipBaselines do { numBaselines++ //print("Baseline " (major b) "." (minor b) (suffix b) " " (user b) " " (dateOf b) " " (annotation b) "\n") } progressStart(dbMain, "Please Wait", "Reading history from baselines", numBaselines) // load history from current version getHistory(currMod) // get session history records getSessionHistory(currMod) // loop through list of baselines for thisBasline in skipBaselines do { progressStep(++bCount) progressMessage("Reading history from baseline " bCount " of " numBaselines "") if (progressCancelled) { if (confirm("Are you sure you want to cancel?")) { progressStop //delete(skipBaselines) return(false) } } // open the baseline in the background moduleBaseline = load(currMod, thisBasline, false) if (null moduleBaseline) { print("ERROR opening baseline " (major thisBasline) "." (minor thisBasline) " (" (suffix thisBaseline) ")\n") continue } // read history from baseline getHistory(moduleBaseline) close(moduleBaseline) } progressStop() return(true) } /**************************** doCancel Callback on Cancel button on details dialog ****************************/ void doCancel(DB db) { //release(dbDetails) } /**************************** doHelp Callback on help button. ****************************/ void doHelp(DB db) { } /**************************** refreshDisplay ****************************/ void refreshDisplay() { // refresh current selected object (current ModuleRef__) = currMod (current ObjectRef__) = currObj refresh(currMod) empty(dbeListView) set(dbeOldValue, "") set(dbeNewValue, "") if (selectedTab == TAB_OBJECT) { // update dialog to show history records for the selected object set(dbeLabel, "History for Object " identifier(currObj)) showObjectHistory() show(btnPrev) show(btnNext) //show(btnDetails) //inactive(btnDetails) } else if (selectedTab == TAB_MODULE) { set(dbeLabel, "History for Module " fullName(currMod)) showModuleHistory() hide(btnPrev) hide(btnNext) //hide(btnDetails) } else if (selectedTab == TAB_SESSIONS) { set(dbeLabel, "Session history for Module " fullName(currMod)) showSessionHistory() hide(btnPrev) hide(btnNext) //hide(btnDetails) } } /**************************** preSyncFn ****************************/ bool preSyncFn(Trigger t) { // ensure correct current object and module BEFORE object sync (current ModuleRef__) = currMod (current ObjectRef__) = currObj return(true) } /**************************** postSyncFn ****************************/ void postSyncFn(Trigger t) { // we can safely do this because current module and object were // set BEFORE object sync. //print("post sync fired\n") currObj = current Object refreshDisplay() } /**************************** doClose ****************************/ void doClose(DB db) { //if (!null g_mTrigger) //{ // delete g_mTrigger //} if (!null preSyncTrigger) { delete preSyncTrigger } if (!null postSyncTrigger) { delete postSyncTrigger } // TBD delete data astructures hide(dbMain) destroy(dbMain) dbMain = null } /**************************** fnTrigger ****************************/ bool fnTrigger(Trigger xx) { doClose(dbMain) return true } /**************************** doPrev ****************************/ void doPrev(DBE dbe) { Object oPrev = previous currObj if (oPrev != null) { currObj = oPrev currObjNumber = currObj."Absolute Number" refreshDisplay() } } /**************************** doNext ****************************/ void doNext(DBE dbe) { Object oNext = next currObj if (oNext != null) { currObj = oNext currObjNumber = currObj."Absolute Number" refreshDisplay() } } /**************************** doSelect In shareable edit mode the user may have moved to an object that is not locked. Therefore we must check that the current object is locked for edit. ****************************/ void doSelect(DBE dbe, int iSelected) { string sNew = "" string sOld = "" // get record number from listview - this gives an index to old and new values lists int record = intOf(getColumnValue(dbeListView, iSelected, HIST_RECORD_COLUMN)) if (find(skipNewValues, record, sNew)) { set(dbeNewValue, sNew) if (find(skipOldValues, record, sOld)) { set(dbeOldValue, sOld) } } } /**************************** doUnselect ****************************/ void doUnselect(DBE dbe, int iSelected) { set(dbeOldValue, "") set(dbeNewValue, "") } /**************************** doActivate Callback when user double-clicks on entry in the list view ****************************/ void doActivate(DBE dbe, int iSelected) { //doDetails(dbeListView) } /**************************** doTabSelect ****************************/ void doTabSelect(DBE dbe) { selectedTab = get(dbeTab) refreshDisplay() } /**************************** showHistoryDialog ****************************/ void showHistoryDialog(Skip skip) { string dispString = "" currMod = current Module // trigger to close dialog when module closes //g_mTrigger = trigger(module, close, 10, fnTrigger) dbMain = smartDialog(currMod, "Smart History Viewer", styleCentered | styleFloating) dbeTab = tab(dbMain, tabLabels, 690, 440, doTabSelect) dbeTab->"bottom"->"form" dbeTab->"right"->"form" dbeLabel = label(dbMain, "History for Object " identifier(currObj)) dbeLabel->"top"->"inside"->dbeTab dbeLabel->"left"->"inside"->dbeTab dbeListView = listView(dbMain, listViewOptionSortText, 650, 10, columnTitles) dbeListView->"top"->"spaced"->dbeLabel dbeListView->"left"->"inside"->dbeTab dbeListView->"right"->"inside"->dbeTab dbeListView->"bottom"->"unattached" dbeDetailsFrame = frame(dbMain, "Details of selected history record", 650, 100) dbeDetailsFrame->"top"->"spaced"->dbeListView dbeDetailsFrame->"left"->"inside"->dbeTab dbeDetailsFrame->"right"->"inside"->dbeTab dbeDetailsFrame->"bottom"->"unattached" dbeOldValue = richText(dbMain, "Old value", "", 325, 100, true) dbeOldValue->"top"->"inside"->dbeDetailsFrame dbeOldValue->"left"->"inside"->dbeDetailsFrame dbeOldValue->"right"->"unattached" dbeOldValue->"bottom"->"inside"->dbeDetailsFrame dbeNewValue = richText(dbMain, "New value", "", 325, 100, true) dbeNewValue->"top"->"inside"->dbeDetailsFrame dbeNewValue->"left"->"spaced"->dbeOldValue dbeNewValue->"right"->"inside"->dbeDetailsFrame dbeNewValue->"bottom"->"inside"->dbeDetailsFrame btnPrev = button(dbMain, "< Previous", doPrev, styleStandardSize) btnPrev->"top"->"spaced"->dbeDetailsFrame btnPrev->"left"->"inside"->dbeTab btnPrev->"bottom"->"inside"->dbeTab btnPrev->"right"->"unattached" // Next button btnNext = button(dbMain, "Next >", doNext, styleStandardSize) btnNext->"top"->"spaced"->dbeDetailsFrame btnNext->"left"->"spaced"->btnPrev btnNext->"bottom"->"inside"->dbeTab btnNext->"right"->"unattached" // hide the close button, but associate callback with dialog close(dbMain, false, doClose) // Help button ok(dbMain, "Close", doClose) apply(dbMain, "Help", doHelp) realize dbMain insertColumn(dbeListView, HIST_AUTHOR_COLUMN, "Author", 120, null) insertColumn(dbeListView, HIST_SESSION_COLUMN, "Session", 50, null) insertColumn(dbeListView, HIST_DATE_COLUMN, "Date", 120, null) insertColumn(dbeListView, HIST_BASELINE_COLUMN, "Baseline", 70, null) insertColumn(dbeListView, HIST_DESCRIPTION_COLUMN, "Description", 250, null) insertColumn(dbeListView, HIST_RECORD_COLUMN, "Record", 50, null) // associate sort callbacks with each column set(dbeListView, HIST_AUTHOR_COLUMN, doSortString) set(dbeListView, HIST_SESSION_COLUMN, doSortInteger) set(dbeListView, HIST_DATE_COLUMN, doSortDate) set(dbeListView, HIST_BASELINE_COLUMN, doSortString) set(dbeListView, HIST_DESCRIPTION_COLUMN, doSortString) set(dbeListView, HIST_RECORD_COLUMN, doSortInteger) setSortColumn(dbeListView, HIST_DATE_COLUMN) // callbacks on listview set(dbeListView, doSelect, doUnselect, doActivate) // resizing setExtraHeightShare(dbeListView, 1.0) setExtraWidthShare(dbeListView, 1.0) setExtraWidthShare(dbeOldValue, 0.5) setExtraWidthShare(dbeNewValue, 0.5) // load history from baselines if (getBaselineHistory(skip)) { //printObjectHistory() // history loaded successfully - refresh display refreshDisplay() // It is important to reset the current module before creating the triggers (current ModuleRef__) = currMod // set up triggers for object sync if (!null preSyncTrigger) { delete preSyncTrigger } preSyncTrigger = trigger(module->object, sync, 10, preSyncFn) if (!null postSyncTrigger) { delete postSyncTrigger } postSyncTrigger = trigger(module->object, sync , 10, postSyncFn) show(dbMain) } else { // loading of history was cancelled by user hide(dbMain) destroy(dbMain) dbMain = null } } /**************************** MAIN ****************************/ currMod = current Module currObj = current Object if (null currMod) { infoBox("This utility can only be run from a formal module.") halt } if (null currObj) { infoBox("No current Object!") halt } Skip skip = create // lauch dialog for user to select baselines if (!bsGetUserSelectedBaselines(currMod, skip, "Smart History Viewer", "Please select the baselines for which you would like to view history.\n" //- "Note that loading numerous baselines may take some time.", true)) { halt } showHistoryDialog(skip)