DIRECTORY Atom, BasicTime, Booting, DB, DBIcons, Nut, Process, Rope, ViewerClasses USING [Viewer, ViewerRec], ViewerLocks USING [CallUnderWriteLock], ViewerOps, ViewerTools, UserCredentials, WhiteboardDB, WhiteboardDBPrivate, WhiteboardDump, WhiteboardLoad, WhiteboardViewers; WhiteboardDBImpl: CEDAR MONITOR IMPORTS BasicTime, Booting, DB, DBIcons, Nut, Process, Rope, ViewerLocks, ViewerOps, ViewerTools, WhiteboardDBPrivate, WhiteboardViewers, WhiteboardDump, WhiteboardLoad EXPORTS WhiteboardDB = BEGIN OPEN DB, WhiteboardDBPrivate; Viewer: TYPE = ViewerClasses.Viewer; ROPE: TYPE = Rope.ROPE; WhiteboardLog: TYPE = LIST OF WhiteboardLogEntry; WhiteboardLogEntry: TYPE = RECORD[action: ATOM, to: Viewer]; whiteboard: PUBLIC ATOM _ WhiteboardDBPrivate.wbType; destroyedList: LIST OF Viewer; ticksToWait: Process.Ticks _ Process.SecondsToTicks[5*60]; stopped: PUBLIC BOOL _ FALSE; -- true if the Rollback proc has been executed and the transaction is to remain closed doingRestart: BOOL _ FALSE; -- true during the restart process, to keep iconic viewers from being opened activity: BOOLEAN _ TRUE; -- true if a database operation has been performed recently WBError: PUBLIC ERROR[reason: ATOM] = CODE; -- the error that may be raised from this interface WatchDBActivity: PROC[] = { WHILE TRUE DO Process.Pause[ticksToWait]; CheckConnection[] ENDLOOP }; CheckConnection: ENTRY PROC[] = { ENABLE UNWIND => NULL; aborted: BOOLEAN _ FALSE; IF stopped THEN RETURN; IF NOT activity THEN WhiteboardDBPrivate.CloseTransaction[ ]; activity _ FALSE }; Close: PUBLIC ENTRY PROC[] = { ENABLE UNWIND => NULL; WhiteboardDBPrivate.CloseTransaction[ ]; stopped _ TRUE }; Display: PUBLIC ENTRY PROC[eName: ROPE, v: Viewer _ NIL] RETURNS [viewer: Viewer] = BEGIN ENABLE UNWIND => NULL; DoDisplay: PROC[] = { viewer _ InternalDisplay[eName, v] }; CarefullyApply[DoDisplay] END; InternalDisplay: PROC[eName: ROPE, v: Viewer _ NIL] RETURNS [Viewer] = BEGIN locked: BOOLEAN _ FALSE; wb: Whiteboard = DeclareWhiteboard[eName]; children: ChildSet = Children[wb]; newViewer: BOOL _ FALSE; nextChild: WBItem; child: Viewer; x, y: INT; DisplayNoteEntity: PROC[] = BEGIN text: ViewerTools.TiogaContents = GetContents[nextChild]; w, h: INT; [w, h] _ Size[nextChild]; child _ WhiteboardViewers.AddTextBox[v, x, y, w, h, text] END; DisplayIconEntity: PROC[] = BEGIN label, icon: ROPE; [label, icon] _ GetDisplayProps[nextChild]; child _ WhiteboardViewers.AddIcon[v, label, DBIcons.GetIcon[icon, tool], x, y]; END; DoDisplay: PROC = { versionNumber: INTEGER; IF NOT v.newVersion THEN { v.newVersion _ TRUE; WhiteboardViewers.SetMenu[v]; WhiteboardViewers.SetWBName[v, eName]; }; BEGIN gridSize: INT = GetGridSize[wb]; WhiteboardViewers.ResetGrid[v, gridSize] END; versionNumber _ GetVersion[wb]; ViewerOps.AddProp[v, $Version, NEW[INTEGER _ versionNumber]]; WHILE (nextChild _ NextChild[children]) # NIL DO [x, y] _ Position[nextChild]; IF TypeOf[nextChild] = note THEN DisplayNoteEntity[] ELSE DisplayIconEntity[]; StoreEntity[child, DB.NameOf[nextChild]]; ENDLOOP; v.newVersion _ FALSE; ViewerOps.PaintViewer[v, all] }; BEGIN ENABLE UNWIND => { IF newViewer THEN ViewerOps.DestroyViewer[v] }; IF v = NIL OR v.class.flavor # WhiteboardViewers.wb THEN { v _ ViewerOps.CreateViewer[flavor: WhiteboardViewers.wb, info: [name: eName, iconic: TRUE]]; newViewer _ TRUE } ELSE v.child _ NIL; -- flushes viewer ViewerOps.AddProp[v, $Log, NIL]; -- establish log [] _ Nut.SetFrozenProperty[v, FALSE]; Nut.SetNutInfo[v, $Whiteboard, "Whiteboard", eName]; ViewerOps.AddProp[v, $Lines, NIL]; ViewerLocks.CallUnderWriteLock[DoDisplay, v]; IF NOT doingRestart AND v.iconic AND NOT v.destroyed THEN ViewerOps.OpenIcon[v]; RETURN[v] END END; Destroy: PUBLIC ENTRY PROC[eName: ROPE] = BEGIN ENABLE UNWIND => NULL; DoDestroy: PROC[] = { wb: Whiteboard = IF Exists[eName] THEN DeclareWhiteboard[eName] ELSE NIL; IF WhiteboardDBPrivate.readOnly THEN WBError[$ReadOnly]; IF wb = NIL THEN RETURN ELSE DestroyWhiteboard[wb]; WhiteboardDBPrivate.MarkTransaction[] }; v: Viewer; CarefullyApply[DoDestroy]; v _ ViewerOps.FindViewer[Rope.Cat["Whiteboard: ", eName]]; IF v # NIL THEN ViewerOps.DestroyViewer[v]; END; New: PUBLIC ENTRY PROC[eName: ROPE] RETURNS[success: BOOL] = BEGIN ENABLE UNWIND => NULL; wbEntity: DB.Entity; DoNew: PROC[] = { IF WhiteboardDBPrivate.readOnly THEN WBError[$ReadOnly]; wbEntity _ DeclareWhiteboard[eName]; WhiteboardDBPrivate.MarkTransaction[] }; CarefullyApply[DoNew]; success _ wbEntity # NIL END; Reset: PUBLIC ENTRY PROC[wb: Viewer] = BEGIN ENABLE UNWIND => NULL; wb.newVersion _ TRUE; ViewerOps.PaintViewer[wb, caption]; WhiteboardDBPrivate.CloseTransaction[]; NARROW[ViewerOps.FetchProp[wb, $Version], REF INTEGER]^ _ -1; InternalReset[wb]; END; InternalReset: INTERNAL PROC[wb: Viewer] = BEGIN DoRedisplay: PROC[] = { name: ROPE = GetWBName[wb]; IF WhiteboardDBPrivate.Exists[name] THEN { wbEntity: Whiteboard = DeclareWhiteboard[name]; version: INTEGER = GetVersion[wbEntity]; IF version # NARROW[ViewerOps.FetchProp[wb, $Version], REF INTEGER]^ THEN [] _ InternalDisplay[name, wb] } ELSE ViewerOps.DestroyViewer[wb] }; CarefullyApply[DoRedisplay]; END; Save: PUBLIC ENTRY PROC[wb: Viewer] = BEGIN ENABLE UNWIND => NULL; eventLog: WhiteboardLog = NARROW[ViewerOps.FetchProp[wb, $Log], WhiteboardLog]; versionNumber: INTEGER = NARROW[ViewerOps.FetchProp[wb, $Version], REF INTEGER]^; name: Rope.ROPE = GetWBName[wb]; DoSave: PROC[] = { IF WhiteboardDBPrivate.readOnly THEN WBError[$ReadOnly]; BEGIN wbEntity: Whiteboard _ DeclareWhiteboard[name]; IF GetVersion[wbEntity] # versionNumber THEN WBError[$WrongVersion]; FOR log: WhiteboardLog _ eventLog, log.rest UNTIL log = NIL DO SELECT log.first.action FROM $NewIcon => DoNewIcon[wbEntity, log.first.to]; $NewBox => DoNewBox[wbEntity, log.first.to]; $Delete => DoDelete[log.first.to]; $EditText => DoEditText[log.first.to]; $Grow => DoGrow[log.first.to]; $Move => DoMove[log.first.to]; $Erase => wbEntity _ DoErase[wbEntity]; ENDCASE; ENDLOOP; SetGridSize[wbEntity, WhiteboardViewers.GetGrid[wb]]; SetVersion[wbEntity, versionNumber + 1]; SetCreateDate[wbEntity, BasicTime.Now[]]; WhiteboardDBPrivate.MarkTransaction[] END }; [] _ wb.class.scroll[wb, thumb, 0]; CarefullyApply[DoSave]; DestroyViewerList[]; [] _ InternalDisplay[name, wb] END; DestroyViewerList: PROC[] = { FOR l: LIST OF Viewer _ destroyedList, l.rest UNTIL l = NIL DO l.first.props _ NIL; l.first.data _ NIL ENDLOOP; destroyedList _ NIL }; GetWBName: PUBLIC PROC[wb: Viewer] RETURNS [name: ROPE] = BEGIN domain: ROPE; [domain ~ domain, entity ~ name] _ Nut.GetNutInfo[wb]; IF Rope.Equal[domain, "Whiteboard"] THEN RETURN; RETURN[NIL] END; WBExists: PUBLIC ENTRY PROC[eName: ROPE] RETURNS [alreadyExists: BOOL] = BEGIN ENABLE UNWIND => NULL; DoCheck: PROC[] = {alreadyExists _ Exists[eName]}; CarefullyApply[DoCheck]; END; GetCreateDate: PUBLIC ENTRY PROC[eName: ROPE] RETURNS [date: BasicTime.GMT] = BEGIN ENABLE UNWIND => NULL; DoGet: PROC[] = { wb: Whiteboard = DeclareWhiteboard[eName]; date _ WhiteboardDBPrivate.GetCreateDate[wb]}; CarefullyApply[DoGet]; END; CopyWB: PUBLIC ENTRY PROC[from, to: ROPE] = BEGIN ENABLE UNWIND => NULL; DoCopy: INTERNAL PROC[] = { IF WhiteboardDBPrivate.readOnly THEN WBError[$ReadOnly]; BEGIN fromWB: Whiteboard = DeclareWhiteboard[from]; toWB: Whiteboard = DeclareWhiteboard[to]; children: ChildSet = Children[fromWB]; FOR child: WBItem _ NextChild[children], NextChild[children] UNTIL child = NIL DO IF TypeOf[child] = note THEN CopyNote[toWB, child] ELSE CopyIcon[toWB, child] ENDLOOP; WhiteboardDBPrivate.MarkTransaction[] END }; CarefullyApply[DoCopy] END; CopyNote: INTERNAL PROC[to: Whiteboard, note: WBNote] = BEGIN text: ViewerTools.TiogaContents = GetContents[note]; x, y: INT; w, h: INT; [x, y] _ Position[note]; [w, h] _ Size[note]; [] _ NewNote[to, x, y, w, h, text] END; CopyIcon: INTERNAL PROC[to: Whiteboard, icon: WBIcon] = BEGIN x, y: INT; name, label, iconName, argument: ROPE; type: ATOM; [x, y] _ Position[icon]; [label, iconName] _ GetDisplayProps[icon]; [name, type] _ WhiteboardDBPrivate.GetIconProps[icon]; argument _ WhiteboardDBPrivate.GetToolArgument[icon]; [] _ WhiteboardDBPrivate.NewIcon[to, x, y, name, label, iconName, argument, type] END; GetChildren: PUBLIC ENTRY PROC[eName: ROPE _ NIL] RETURNS [wbList: LIST OF ROPE] = BEGIN ENABLE UNWIND => NULL; EnumWBs: PROC[] = { entityList: LIST OF WBItem; children: ChildSet = Children[DeclareWhiteboard[eName], TRUE]; FOR child: WBItem _ NextChild[children], NextChild[children] UNTIL child = NIL DO entityList _ CONS[child, entityList]; ENDLOOP; FOR eL: LIST OF Whiteboard _ entityList, eL.rest UNTIL eL = NIL DO wbList _ CONS[WhiteboardDBPrivate.GetIconProps[eL.first].name, wbList]; ENDLOOP }; CarefullyApply[EnumWBs] END; Enumerate: PUBLIC ENTRY PROC[pattern: ROPE _ NIL] RETURNS [wbList: LIST OF ROPE] = BEGIN ENABLE UNWIND => NULL; EnumWBs: PROC[] = { entityList: LIST OF Whiteboard = WhiteboardDBPrivate.Enumerate[pattern]; FOR eL: LIST OF Whiteboard _ entityList, eL.rest UNTIL eL = NIL DO wbList _ CONS[DB.NameOf[eL.first], wbList]; ENDLOOP }; CarefullyApply[EnumWBs] END; Dump: PUBLIC ENTRY PROC[to: ROPE] = { ENABLE UNWIND => NULL; DoDump: INTERNAL PROC[] = {WhiteboardDump.DumpToFile[to] }; CarefullyApply[DoDump] }; Load: PUBLIC ENTRY PROC[from: ROPE] = { ENABLE UNWIND => NULL; DoLoad: INTERNAL PROC[] = { IF WhiteboardDBPrivate.readOnly THEN WBError[$ReadOnly]; WhiteboardLoad.LoadFromFile[from] }; CarefullyApply[DoLoad] }; NewIcon: PUBLIC ENTRY PROC[wb: Viewer, x, y: INT, name: ROPE, type: ATOM, icon: ROPE, label: ROPE _ NIL, argument: ROPE _ NIL] RETURNS [new: Viewer] = BEGIN ENABLE UNWIND => NULL; new _ WhiteboardViewers.AddIcon[wb, IF Rope.Equal[label, ""] THEN name ELSE label, DBIcons.GetIcon[icon, tool], x, y]; ViewerOps.AddProp[new, $EntityName, name]; ViewerOps.AddProp[new, $IconLabel, label]; ViewerOps.AddProp[new, $IconType, type]; ViewerOps.AddProp[new, $IconIcon, icon]; ViewerOps.AddProp[new, $IconArgument, argument]; LogAction[wb, $NewIcon, new]; ViewerOps.PaintViewer[wb, caption]; END; NewBox: PUBLIC ENTRY PROC[wb: Viewer, x, y, w, h: INT, contents: ViewerTools.TiogaContents _ NIL] RETURNS [new: Viewer] = BEGIN ENABLE UNWIND => NULL; new _ WhiteboardViewers.AddTextBox[wb, x, y, w, h, contents]; LogAction[wb, $NewBox, new]; ViewerOps.PaintViewer[new, all]; ViewerOps.PaintViewer[wb, caption]; END; Delete: PUBLIC ENTRY PROC[child: Viewer] = BEGIN ENABLE UNWIND => NULL; logIt: BOOL = NOT WhiteboardViewers.DontLog[child]; props: Atom.PropList = child.props; data: REF ANY = child.data; IF logIt THEN LogAction[child.parent, $Delete, child]; ViewerOps.DestroyViewer[viewer: child, paint: FALSE]; IF logIt THEN {child.props _ props; child.data _ data} END; EditText: PUBLIC ENTRY PROC[box: Viewer] = BEGIN ENABLE UNWIND => NULL; LogAction[box.parent, $EditText, box]; END; Grow: PUBLIC ENTRY PROC[box: Viewer] = BEGIN ENABLE UNWIND => NULL; LogAction[box.parent, $Grow, box]; END; Move: PUBLIC ENTRY PROC[child: Viewer] = BEGIN ENABLE UNWIND => NULL; IF NOT WhiteboardViewers.DontLog[child] THEN LogAction[child.parent, $Move, child]; END; Erase: PUBLIC ENTRY PROC[wb: Viewer] = BEGIN ENABLE UNWIND => NULL; LogAction[wb, $Erase, wb]; wb.child _ NIL; -- flushes viewer ViewerOps.PaintViewer[wb, client]; END; LogAction: PROC[wb: Viewer, action: ATOM, v: Viewer] = BEGIN log: WhiteboardLog _ NARROW[ViewerOps.FetchProp[wb, $Log], WhiteboardLog]; AddToEnd: PROC[log: WhiteboardLog, entry: WhiteboardLogEntry] RETURNS[WhiteboardLog] = { IF log = NIL THEN RETURN[LIST[entry]] ELSE RETURN[CONS[log.first, AddToEnd[log.rest, entry]]] }; log _ AddToEnd[log, [action, v]]; ViewerOps.AddProp[wb, $Log, log]; END; DoNewIcon: PROC[wb: Whiteboard, child: Viewer] = BEGIN name: ROPE = NARROW[ViewerOps.FetchProp[child, $EntityName], ROPE]; label: ROPE = NARROW[ViewerOps.FetchProp[child, $IconLabel], ROPE]; type: ATOM = NARROW[ViewerOps.FetchProp[child, $IconType], ATOM]; icon: ROPE = NARROW[ViewerOps.FetchProp[child, $IconIcon], ROPE]; argument: ROPE = NARROW[ViewerOps.FetchProp[child, $IconArgument], ROPE]; newIcon: WBItem = WhiteboardDBPrivate.NewIcon[wb, child.wx, child.wy, name, label, icon, argument, type]; StoreEntity[child, DB.NameOf[newIcon]] -- in case subsequent operations in the log reference this viewer (eg., to move it) END; DoNewBox: PROC[wb: Whiteboard, box: Viewer] = BEGIN text: ViewerTools.TiogaContents = ViewerTools.GetTiogaContents[box]; newNote: WBItem = WhiteboardDBPrivate.NewNote[wb, box.wx, box.wy, box.ww, box.wh, text]; StoreEntity[box, DB.NameOf[newNote]] -- in case subsequent operations in the log reference this viewer (eg., to move it) END; DoDelete: PROC[child: Viewer] = BEGIN entity: WBItem = FetchEntity[child]; WhiteboardDBPrivate.Destroy[entity]; destroyedList _ CONS[child, destroyedList]; END; DoEditText: PROC[box: Viewer] = BEGIN note: WBNote = FetchEntity[box]; text: ViewerTools.TiogaContents = ViewerTools.GetTiogaContents[box]; SetContents[note, text] END; DoGrow: PROC[box: Viewer] = BEGIN note: WBNote = FetchEntity[box]; WhiteboardDBPrivate.Grow[note, box.ww, box.wh] END; DoMove: PROC[child: Viewer] = BEGIN entity: WBItem = FetchEntity[child]; WhiteboardDBPrivate.Move[entity, child.wx, child.wy] END; DoErase: PROC[wb: Whiteboard] RETURNS[newWB: Whiteboard] = BEGIN name: ROPE = NameOf[wb]; WhiteboardDBPrivate.DestroyWhiteboard[wb]; newWB _ DeclareWhiteboard[name] END; FetchEntity: PUBLIC PROC[v: Viewer] RETURNS[e: DB.Entity] = { name: ROPE = NARROW[ViewerOps.FetchProp[v, $Entity], ROPE]; IF name = NIL THEN RETURN[NIL]; SELECT v.class.flavor FROM WhiteboardViewers.text => e _ FetchWBItem[name, note]; WhiteboardViewers.icon => e _ FetchWBItem[name, icon]; ENDCASE => e _ NIL }; StoreEntity: PUBLIC PROC[v: Viewer, name: ROPE] = { ViewerOps.AddProp[v, $Entity, name ] }; GetIconProps: PUBLIC ENTRY PROC[wbIcon: Viewer] RETURNS[ name: ROPE, type: ATOM ] = { ENABLE UNWIND => NULL; DoGet: PROC[] = { iconEntity: WBIcon = FetchEntity[wbIcon]; [name, type] _ WhiteboardDBPrivate.GetIconProps[iconEntity] }; name _ NARROW[ViewerOps.FetchProp[wbIcon, $EntityName]]; IF name # NIL THEN type _ NARROW[ViewerOps.FetchProp[wbIcon, $IconType]] ELSE CarefullyApply[DoGet] }; GetToolArgument: PUBLIC ENTRY PROC[wbIcon: Viewer] RETURNS[ argument: ROPE] = { ENABLE UNWIND => NULL; DoGet: PROC[] = { iconEntity: WBIcon = FetchEntity[wbIcon]; IF iconEntity = NIL THEN {argument _ NIL; RETURN}; argument _ WhiteboardDBPrivate.GetToolArgument[iconEntity] }; argument _ NARROW[ViewerOps.FetchProp[wbIcon, $IconArgument]]; IF argument = NIL THEN CarefullyApply[DoGet]; ViewerOps.AddProp[wbIcon, $IconArgument, argument] }; EstablishWhiteboardDB: PUBLIC ENTRY PROC[DBFile: ROPE _ NIL] = { ENABLE UNWIND => NULL; IF DBFile = NIL THEN { IF WhiteboardDBPrivate.WBSegment # NIL THEN { InternalResetViewers[destroy: FALSE]; stopped _ FALSE } ELSE { stopped _ TRUE; WBError[$NoDatabaseOpen] }; RETURN }; IF NOT Rope.Equal[DBFile, WhiteboardDBPrivate.WBSegment, FALSE] THEN InternalResetViewers[TRUE]; IF WhiteboardDBPrivate.Initialize[DBFile].serverDown THEN ERROR WBError[$ServerDown]; stopped _ FALSE }; InternalResetViewers: INTERNAL PROC[destroy: BOOL] = { IF destroy THEN { DestroyProc: ViewerOps.EnumProc = { IF v.class.flavor = $Whiteboard THEN ViewerOps.DestroyViewer[v]; RETURN[TRUE] }; aborted: BOOL _ FALSE; WhiteboardDBPrivate.CloseTransaction[]; ViewerOps.EnumerateViewers[enum: DestroyProc] } ELSE { ResetProc: INTERNAL ViewerOps.EnumProc = { IF v.class.flavor = $Whiteboard AND NOT v.newVersion AND NOT v.destroyed THEN InternalReset[v]; RETURN[TRUE] }; doingRestart _ TRUE; ViewerOps.EnumerateViewers[enum: ResetProc]; doingRestart _ FALSE } }; CarefullyApply: INTERNAL PROC [proc: PROC[]] ~ { ENABLE BEGIN DB.Error => ERROR WBError[$InternalError]; DB.Failure => ERROR WBError[$ServerDown]; DB.Aborted => ERROR WBError[$TransactionAbort]; END; aborted: BOOL _ FALSE; IF stopped THEN RETURN; -- don't bother trying to do the operation activity _ TRUE; BEGIN ENABLE DB.Aborted => { aborted _ TRUE; CONTINUE }; WhiteboardDBPrivate.ResetSchema[]; proc[] END; IF NOT aborted THEN RETURN; -- no aborted occurred WhiteboardDBPrivate.AbortTransaction[]; WhiteboardDBPrivate.ResetSchema[]; proc[]; -- don't bother trying to restart here -- }; CloseWB: ENTRY Booting.CheckpointProc = { ENABLE UNWIND => NULL; WhiteboardDBPrivate.CloseTransaction[]; stopped _ TRUE }; OpenUp: Booting.RollbackProc = { DB.Initialize[nCachePages: 256] }; NewUserReset: ENTRY UserCredentials.CredentialsChangeProc = { ENABLE UNWIND => NULL; DestroyProc: ViewerOps.EnumProc = { IF v.class.flavor = $Whiteboard THEN ViewerOps.DestroyViewer[v]; RETURN[TRUE] }; WhiteboardDBPrivate.CloseTransaction[]; ViewerOps.EnumerateViewers[enum: DestroyProc] }; TRUSTED { Process.Detach[FORK WatchDBActivity[]]; Booting.RegisterProcs[c: CloseWB, r: OpenUp] } END... òWhiteboardDBImpl.mesa Copyright (C) 1984 by Xerox Corporation. All rights reserved. Last edited by Jennifer Widom, August 24, 1984 8:38:52 pm PDT Donahue, January 27, 1986 7:58:48 am PST (Corrected destruction of viewer properties when playing log; simplified CarefullyApply) (Changed reset to close transaction before doing redisplay so that cached pages aren't used -- this gets the proper stuff redisplayed!) (Added WBError and changed error handling behavior in the code -- now most of it is in CarefullyApply, where it should have been all along) Last Edited by: Winkler, December 18, 1984 4:49:16 pm PST Currently action can be one of: $NewIcon $NewBox $Delete $EditText $Grow $Move $Erase Monitoring database activity Don't keep a transaction open too long. Routines that read and/or write the database If v.newVersion then this is a reset and the name and menu are properly set Must make sure that the grid size is consistent with the current DB contents obtain version information Check if there's a viewer that needs to be destroyed Make sure the viewer gets repainted by setting its version number to nonsense Erase works by destroying the whiteboard and creating a new one Now it's safe to throw away any destroyed viewers (ones on the destroyedList); they either participated in a committed transaction (and thus can have their properties released) or they no longer exist on the whiteboard in any case Commit the transaction Routines that log whiteboard events w/o accessing the database Make sure the properties and clientdata don't go away just yet Playing the log Commit a $NewIcon action Commit a $NewBox action Commit a $Delete action Save the child on the destroyedList so that you can release the props and data if the transaction succeeds Commit an $EditText action Commit a $Grow action Commit a $Move action Commit an $Erase action Support routines Tool manipulation If the entity name is NIL, then don't bother looking in the database for an argument Database initialization Sets newWhiteboardDB to DBFile (if a whiteboard DB was previously opened and the argument was NIL, then remember the previous DB file); also throws away old viewers on previous DB Reopening a previously opened database Switching databases Close transaction if there's one open Destroy all Whiteboard viewers Leave edited or dead whiteboards alone!! The enumeration will fail and stop if the reset fails; this also sets the returned value of success Close transaction if there's one open Destroy all whiteboard viewers Initialization ʆ˜Jšœ™Jšœ=™=šœ™Jšœ.™.J™(J™XJ™‡J™‹J™9J˜—šÏk ˜ J˜J˜ J˜Jšœ˜J˜J˜J˜J˜Jšœœ˜(Jšœ œ˜'J˜ J˜ J˜J˜ Jšœ˜J˜J˜Jšœ˜J˜—šœœ˜šœ˜JšœœŠ˜ —Jšœ˜J˜Jšœœœ˜#J˜—Jšœœ˜$J˜Jšœœœ˜J˜Jšœœœœ˜1J˜šœœœ œ˜<šœ™Jšœ™J™Jšœ™Jšœ ™ Jšœ™Jšœ™J™——J˜Jšœ œœ˜5J˜Jšœœœ˜Ihead1™˜Jšœ:˜:J˜Jšœ œœÏcV˜tJ˜JšœœœžL˜hJ˜Jšœ œœž;˜WJ˜Jš œ œœ œœž3˜`J˜šÏnœœ˜J™'šœœ˜ Jšœ˜Jšœ˜Jšœ˜ —J˜—šŸœœœ˜!Jšœœœ˜Jšœ œœ˜Jšœ œœ˜Jšœœ œ)˜=Jšœ œ˜—J˜šŸœœœœ˜Jšœœœ˜Jšœ(˜(Jšœ œ˜——K™,˜šŸœœœœœœœ˜Sš˜Jšœœœ˜JšŸ œœ,˜;Jšœ˜Jšœ˜——J˜š Ÿœœœœœ ˜Gš˜Jšœœœ˜Jšœ*˜*Jšœ"˜"Jšœ œœ˜J˜J˜Jšœœ˜ šŸœœ˜š˜Jšœ9˜9Jšœœ˜ Jšœ˜Jšœ9˜9Jšœ˜——šŸœœ˜š˜Jšœ œ˜J˜+JšœO˜OJšœ˜——šŸ œœ˜Jšœœ˜JšœK™Kšœœœ˜Jšœœ˜J˜J˜&J˜—JšœL™Lš˜Jšœ œ˜ Jšœ(˜(Jšœ˜—J™Jšœ˜Jšœœœ˜=šœ%œ˜0Jšœ˜Jšœœœ˜NJšœœ˜)Jšœ˜—Jšœœ˜J˜ —J˜š˜Jšœœœ œ˜Bšœœœ'œ˜:JšœUœ˜\Jšœ œ˜—Jšœ œž˜%Jšœœž˜1Jšœœ˜%J˜4Jšœœ˜"J˜-Jš œœœ œœ œ˜PJšœ˜ Jš˜—Jšœ˜——J˜š Ÿœœœœœ˜)š˜Jšœœœ˜šŸ œœ˜Jš œœœœœ˜IJšœœ˜8Jš œœœœœ˜3Jšœ%˜%Jšœ˜—J˜ Jšœ˜J™4J˜:Jšœœœ˜+Jšœ˜——J˜J˜šŸœœœœœœ œ˜<š˜Jšœœœ˜Jšœ œ˜šŸœœ˜Jšœœ˜8Jšœ$˜$Jšœ(˜(—Jšœ˜Jšœ˜Jšœ˜——J˜šŸœœœœ˜&š˜Jšœœœ˜Jšœœ˜J˜#Jšœ'˜'JšœM™MJšœ$œœ˜=Jšœ˜Jšœ˜——J˜šŸ œœœ˜*š˜šŸ œœ˜Jšœœ˜šœ"œ˜*Jšœ/˜/Jšœ œ˜(Jšœ œ$œœ˜DJšœ!˜%—Jšœ˜#—Jšœ˜Jšœ˜J˜——šŸœœœœ˜%š˜Jšœœœ˜Jšœœ/˜OJš œœœ$œœ˜QJšœ œ˜ šŸœœ˜Jšœœ˜8š˜Jšœ/˜/Jšœ&œ˜Dšœ)œœ˜>šœ˜Jšœ.˜.Jšœ,˜,Jšœ"˜"Jšœ&˜&Jšœ˜Jšœ˜šœ'˜'Jšœ?™?—Jšœ˜—Jšœ˜—Jšœ5˜5Jšœ(˜(Jšœ)˜)Jšœ%˜%Jšœ˜——Jšœ#˜#J˜Jšœæ™æJšœ˜Jšœ˜Jšœ˜——J˜šŸœœ˜š œœœ œœ˜>Jšœœ˜Jšœ˜Jšœ˜—Jšœœ˜—J˜š Ÿ œœœ œœ˜9š˜Jšœœ˜ Jšœ6˜6Jšœ"œœ˜0Jšœœ˜ Jšœ˜——J˜šŸœœœœœœœ˜Hš˜Jšœœœ˜JšŸœœ&˜3Jšœ˜Jšœ˜——J˜šŸ œœœœœœœ˜Mš˜Jšœœœ˜šŸœœ˜Jšœ*˜*Jšœ/˜/—Jšœ˜Jšœ˜——J˜š Ÿœœœœ œ˜+š˜Jšœœœ˜šŸœœœ˜Jšœœ˜8š˜Jšœ-˜-Jšœ)˜)Jšœ&˜&šœ:œ œ˜Qšœœ˜2Jšœ˜—Jšœ˜—J™Jšœ%˜%Jšœ˜——Jšœ˜Jšœ˜——J˜J˜šŸœ œ ˜7š˜J˜4Jšœœ˜ Jšœœ˜ J˜J˜Jšœ"˜"Jšœ˜——J˜šŸœœœ ˜7š˜Jšœœ˜ Jšœ!œ˜&Jšœœ˜ Jšœ˜Jšœ*˜*Jšœ6˜6J˜5JšœQ˜QJšœ˜——J˜šŸ œœœœœœœ œœœ˜Rš˜Jšœœœ˜šŸœœ˜Jšœ œœ˜Jšœ8œ˜>šœ:œ œ˜QJšœ œ˜%Jšœ˜—š œœœ"œœ˜BJšœ œ:˜GJšœ˜ ——Jšœ˜Jšœ˜——J˜šŸ œœœœ œœœ œœœ˜Rš˜Jšœœœ˜šŸœœ˜Jšœ œœ5˜Hš œœœ"œœ˜BJšœ œœ˜+Jšœ˜ ——Jšœ˜Jšœ˜——J˜š Ÿœœœœœ˜%Jšœœœ˜JšŸœœœ&˜;Jšœ˜—J˜š Ÿœœœœœ˜'Jšœœœ˜šŸœœœ˜Jšœœ˜8Jšœ$˜$—Jšœ˜——K™>˜šŸœœœœœœœœ œœ œœœ˜–š˜Jšœœœ˜Jšœ$œœœ+˜vJšœ*˜*J˜*Jšœ(˜(J˜(Jšœ0˜0J˜J˜#Jšœ˜——J˜šŸœœœœœ(œœ˜yš˜Jšœœœ˜Jšœ=˜=J˜J˜ J˜#Jšœ˜—J˜—šŸœœœœ˜*š˜Jšœœœ˜Jšœœœ"˜3Jšœ$˜$Jšœœœ˜Jšœ>™>Jšœœ)˜6Jšœ.œ˜5Jšœœ)˜6šœ˜J˜———šŸœœœœ˜*š˜Jšœœœ˜Jšœ&˜&Jšœ˜—J˜—šŸœœœœ˜&š˜Jšœœœ˜Jšœ"˜"Jšœ˜J˜——šŸœœ œ˜(š˜Jšœœœ˜šœœ"œ˜-J˜&—Jšœ˜—J˜—šŸœœœœ˜&š˜Jšœœœ˜Jšœ˜Jšœ œž˜!J˜"Jšœ˜——J˜šŸ œœœ˜6š˜Jšœœ/˜JšŸœœ/˜=šœ˜š œœœœœ˜%Jšœœœ*˜:———Jšœ!˜!Jšœ!˜!Jšœ˜———K™˜šŸ œœ!˜0Jšœ™š˜Jšœœœ*œ˜CJšœœœ)œ˜CJšœœœ(œ˜AJšœœœ(œ˜AJšœ œœ,œ˜IJšœi˜iJšœœžS˜zJšœ˜——J˜JšŸœœ˜-šœ™š˜J˜DJšœX˜XJšœœžS˜xJšœ˜——J˜šŸœœ˜Jšœ™š˜Jšœ$˜$Jšœ$˜$Jšœj™jJšœœ˜+Jšœ˜——J˜JšŸ œœ˜šœ™š˜Jšœ ˜ J˜DJ˜Jšœ˜——J˜JšŸœœ˜šœ™š˜Jšœ ˜ J˜.Jšœ˜J˜——JšŸœœ˜šœ™š˜Jšœ$˜$J˜4Jšœ˜——J˜šŸœœœ˜:šœ™š˜Jšœœ˜Jšœ*˜*Jšœ˜Jšœ˜————K™˜š Ÿ œœœ œœ ˜=Jšœœœ"œ˜;Jš œœœœœ˜šœ˜Jšœ6˜6Jšœ6˜6—šœ œ˜J˜——šŸ œœœœ˜3Jšœ'˜'——K™˜šŸ œœœœœœœ˜UJšœœœ˜šŸœœ˜Jšœ)˜)Jšœ>˜>—Jšœœ+˜8šœœ˜Jšœœ(˜5—Jšœ˜—J˜š Ÿœœœœœ œ˜OJšœœœ˜šŸœœ˜Jšœ)˜)JšœT™TJš œœœ œœ˜2Jšœ=˜=—Jšœ œ-˜>Jšœ œœ˜-Jšœ5˜5——™š Ÿœœœœ œœ˜@Jšœ³™³Jšœœœ˜šœ œœ˜šœ!œœ˜-Jšœ&™&Jšœœ˜%Jšœ œ˜—Jšœ œ˜2Jšœ˜ —Jšœ™Jš œœ3œœœ˜`Jšœ3œœ˜UJšœ ˜Jšœ˜—J˜J˜šŸœœœ œ˜6šœ œ˜šœ#˜#Jšœœ˜@Jšœœ˜—Jšœ œœ˜J™%Jšœ'˜'J™Jšœ/˜/—šœ˜šœ œ˜*Jšœ(™(š œœœœœ ˜HJšœ˜—Jšœœ˜—Jšœœ˜Jšœ,˜,Jšœc™cJšœœ˜—J˜—šŸœœœœ˜0šœ˜ Jšœ œ˜*Jšœ œ˜)Jšœ œ˜/Jšœ˜—Jšœ œœ˜Jšœ œœž*˜CJšœ œ˜š˜Jšœœœœ˜2Jšœ"˜"Jšœ˜Jšœ˜—Jš œœ œœž˜2Jšœ'˜'Jšœ"˜"JšœÏbž)˜1Jšœ˜—J˜šœ œ˜)Jšœœœ˜Jšœ'˜'šœ œ˜J˜——šœ œ ˜CJ˜—šœœ*˜=Jšœœœ˜šœ#˜#Jšœœ˜@Jšœœ˜—J™%Jšœ'˜'J™Jšœ0˜0——K™˜šœ˜ Jšœœ˜'Jšœ.˜.——J˜Jšœ˜J˜—…—Bd’