<> <> <> <> DIRECTORY Atom USING [GetPName, PropList], Buttons USING [Button, ButtonProc, Create, ReLabel, SetDisplayStyle], Commander USING [CommandProc, Register], Containers USING [Create], Desktops, FS USING [Error, StreamOpen], Imager, ImagerBackdoor, InputFocus USING [SetInputFocus], IO USING [atom, bool, Close, EndOf, EndOfStream, GetAtom, GetBool, GetChar, GetInt, GetRopeLiteral, GetTokenRope, int, Put, PutF, RIS, rope, STREAM], Menus USING [AppendMenuEntry, CreateEntry, CreateMenu, Menu, MenuProc], MessageWindow USING [Append, Blink], Rope USING [Cat, Compare, Equal, Find, Length, ROPE], VFonts USING [EstablishFont, Font], ViewerClasses USING[Column, InitProc, ModifyProc, NotifyProc, PaintProc, SaveProc, ViewerClass, ViewerClassRec, ViewerFlavor, Viewer], ViewerEvents USING [EventProc, RegisterEventProc], ViewerLocks USING [CallUnderViewerTreeLock, Wedged], ViewerOps USING [AddProp, CloseViewer, ChangeColumn, ComputeColumn, CreateViewer, DestroyViewer, EnumerateChildren, EnumerateViewers, EnumProc, FetchProp, FetchViewerClass, FindViewer, GrowViewer, MoveBoundary, MoveViewer, OpenIcon, PaintEverything, PaintViewer, RegisterViewerClass], ViewerSpecs USING [openLeftWidth, openBottomY], ViewerTools USING [GetSelectedViewer, GetSelectionContents]; DeskTopsImpl: CEDAR MONITOR IMPORTS Atom, Buttons, Commander, Containers, FS, Imager, ImagerBackdoor, InputFocus, IO, Menus, MessageWindow, Rope, VFonts, ViewerEvents, ViewerLocks, ViewerOps, ViewerSpecs, ViewerTools EXPORTS Desktops SHARES ViewerOps = BEGIN OPEN Desktops, ViewerClasses; DeskTopData: TYPE = REF DeskTopDataRec; DeskTopDataRec: TYPE = RECORD[ iconBitmap: ImagerBackdoor.Bitmap _ NIL, -- icon bitmap openLeftWidth: INTEGER _ 0, openBottomY: INTEGER _ 0 ]; current: Viewer; MRUCache: Viewer; buttonHeight: INTEGER _ 16; firstButton: INTEGER _ 3; menu: Menus.Menu; iconFont: VFonts.Font _ VFonts.EstablishFont["TimesRoman", 8]; <<******* interface code *******>> <<>> Init: PROC = { desktop, container, button: ViewerClasses.ViewerClass; <> menu _ Menus.CreateMenu[]; Menus.AppendMenuEntry[menu, Menus.CreateEntry["FlyTo", MenuFlyTo]]; Menus.AppendMenuEntry[menu, Menus.CreateEntry["New", MenuCreate]]; Menus.AppendMenuEntry[menu, Menus.CreateEntry["Rename", MenuRename]]; Menus.AppendMenuEntry[menu, Menus.CreateEntry["AddSel", MenuMove]]; Menus.AppendMenuEntry[menu, Menus.CreateEntry["CopySel", MenuCopy]]; Menus.AppendMenuEntry[menu, Menus.CreateEntry["DeleteSel", MenuDelete]]; Menus.AppendMenuEntry[menu, Menus.CreateEntry["Read", MenuRead]]; Menus.AppendMenuEntry[menu, Menus.CreateEntry["Write", MenuWrite]]; button _ ViewerOps.FetchViewerClass[$Button]; button.modify _ ButtonModify; -- handle button selection container _ ViewerOps.FetchViewerClass[$Container]; <> desktop _ NEW[ViewerClassRec _ container^]; desktop.flavor _ $DeskTop; desktop.notify _ DeskTopNotify; desktop.init _ DeskTopInit; desktop.paint _ DeskTopPaint; desktop.menu _ menu; desktop.icon _ document; ContainerInit _ container.init; -- save for future use ViewerOps.RegisterViewerClass[$DeskTop, desktop]; [] _ Buttons.Create[info: [name: "Clean", column: static], proc: ButtonClean]; [] _ ViewerEvents.RegisterEventProc[FlushEntries, destroy, $DeskTop]; Commander.Register["Desktop", DoIt, "Creates a new desktop."]; Commander.Register["ReadDesktop", DoIt, "Reads a desktop from a file on the disk."]; Commander.Register["WriteDesktop", DoIt, "Overwrites a desktop file on the disk."]; }; DoIt: Commander.CommandProc = { token: Rope.ROPE; stream: IO.STREAM; stream _ IO.RIS[cmd.commandLine]; token _ stream.GetTokenRope[].token; stream.Close[]; SELECT TRUE FROM Rope.Equal[cmd.command, "ReadDesktop", FALSE] => ReadFile[token]; Rope.Equal[cmd.command, "WriteDesktop", FALSE] => WriteFile[Create[token]]; ENDCASE => [] _ Create[token]; }; ButtonClean: Buttons.ButtonProc = {ClearIcons[]}; MenuCreate: Menus.MenuProc = {DeskTopNotify[NARROW[parent], LIST[$Create]]}; MenuFlyTo: Menus.MenuProc = {DeskTopNotify[NARROW[parent], LIST[$FlyTo]]}; MenuRename: Menus.MenuProc = {DeskTopNotify[NARROW[parent], LIST[$Rename]]}; MenuMove: Menus.MenuProc = {DeskTopNotify[NARROW[parent], LIST[$MoveViewer]]}; MenuCopy: Menus.MenuProc = {DeskTopNotify[NARROW[parent], LIST[$CopyViewer]]}; MenuDelete: Menus.MenuProc = {DeskTopNotify[NARROW[parent], LIST[$RemoveViewer]]}; MenuRead: Menus.MenuProc = {DeskTopNotify[NARROW[parent], LIST[$Read]]}; MenuWrite: Menus.MenuProc = {DeskTopNotify[NARROW[parent], LIST[$Write]]}; ContainerInit: ViewerClasses.InitProc _ NIL; DeskTopInit: ViewerClasses.InitProc = { data: DeskTopData _ NEW[DeskTopDataRec _ []]; ContainerInit[self]; self.icon _ private; data.openBottomY _ ViewerSpecs.openBottomY; data.openLeftWidth _ ViewerSpecs.openLeftWidth; ViewerOps.AddProp[self, $DeskTopData, data]; IF current = NIL THEN { -- make current current _ self; current.menu _ NIL; }; }; DeskTopPaint: ViewerClasses.PaintProc = { <<[self: Viewer, context: Imager.Context, whatChanged: REF ANY, clear: BOOL];>> data: DeskTopData; IF NOT self.iconic THEN RETURN; data _ FetchDeskTopData[self]; IF data.iconBitmap = NIL THEN PaintDeskTopIcon[self, data]; ImagerBackdoor.DrawBits[context: context, base: data.iconBitmap.base, wordsPerLine: data.iconBitmap.wordsPerLine, sMin: 0, fMin: 0, sSize: data.iconBitmap.height, fSize: data.iconBitmap.width, tx: 0, ty: data.iconBitmap.height]; <> IF ViewerTools.GetSelectedViewer[] = self THEN { Imager.SetColor[context, ImagerBackdoor.invert]; Imager.MaskRectangleI[context, 0, 0, 64, 64]; }; }; darkGrey: Imager.Color ~ ImagerBackdoor.MakeStipple[122645B]; lightGrey: Imager.Color ~ ImagerBackdoor.MakeStipple[012024B]; PaintDeskTopIcon: PROC[self: Viewer, data: DeskTopData] = { bot: INTEGER ~ 13; MapX: PROC [x: NAT] RETURNS [NAT] ~ INLINE { RETURN[(x+8)/16] }; MapY: PROC [y: NAT] RETURNS [NAT] ~ INLINE { RETURN[bot+(y+8)/16] }; w: INTEGER ~ 64; h: INTEGER ~ 64; frobX, frobY: INTEGER; leftOpen, rightOpen: BOOL _ FALSE; context: Imager.Context; <> data.iconBitmap _ ImagerBackdoor.NewBitmap[w, h]; context _ ImagerBackdoor.BitmapContext[data.iconBitmap]; Imager.SetColor[context, Imager.white]; Imager.MaskRectangleI[context, 0, 0, w, h]; <> frobX _ MapX[data.openLeftWidth]; frobY _ MapY[data.openBottomY]; Imager.SetColor[context, Imager.black]; FOR child: Viewer _ self.child, child.sibling WHILE child # NIL DO entry: Entry _ FetchEntryData[child]; IF entry = NIL OR entry.iconic THEN LOOP; SELECT entry.column FROM left => { Imager.MaskRectangleI[context, 0, MapY[entry.wy], frobX, 1]; leftOpen _ TRUE; }; right => { Imager.MaskRectangleI[context, frobX, MapY[entry.wy], w-frobX, 1]; rightOpen _ TRUE; }; ENDCASE; ENDLOOP; <> Imager.SetColor[context, lightGrey]; Imager.MaskRectangleI[context, 0, bot, w, frobY-bot]; SELECT TRUE FROM leftOpen AND rightOpen => NULL; leftOpen => Imager.MaskRectangleI[context, frobX, bot, w-frobX, h-bot]; rightOpen => Imager.MaskRectangleI[context, 0, bot, frobX, h-bot]; ENDCASE => Imager.MaskRectangleI[context, 0, bot, w, h-bot]; Imager.SetColor[context, Imager.black]; IF leftOpen OR rightOpen THEN Imager.MaskRectangleI[context, frobX, frobY, 1, h-frobY]; Imager.MaskRectangleI[context, 0, bot, w, -1]; Imager.MaskRectangleI[context, 0, 0, w, 2]; Imager.MaskRectangleI[context, 0, 0, 2, h]; Imager.MaskRectangleI[context, w, h, -w, -2]; Imager.MaskRectangleI[context, w, h, -2, -h]; <> Imager.SetXYI[context, 3, 4]; Imager.SetFont[context, iconFont]; Imager.ShowRope[context, self.name]; IF self = current THEN { -- invert name region to indicate it is the current desktop Imager.SetColor[context, ImagerBackdoor.invert]; Imager.MaskRectangleI[context, 2, 2, w-4, bot-1-2]; }; }; MyButtonProc: Buttons.ButtonProc = { SELECT TRUE FROM mouseButton = red => DeskTopNotify[NARROW[parent], LIST[$SelectEntry]]; mouseButton = blue => DeskTopNotify[NARROW[parent], LIST[$RemoveViewer]]; shift => DeskTopNotify[NARROW[parent], LIST[$GrowEntry]]; ENDCASE => DeskTopNotify[NARROW[parent], LIST[$OpenEntry]]; }; ButtonModify: ViewerClasses.ModifyProc = { IF change = set THEN Buttons.SetDisplayStyle[self, $BlackOnGrey] ELSE Buttons.SetDisplayStyle[self, $BlackOnWhite]; }; DeskTopNotify: ViewerClasses.NotifyProc = { FOR input _ input, input.rest WHILE input # NIL DO SELECT input.first FROM $ClearIcons => ClearIcons[]; $OpenDesktop, $FlyTo => FlyTo[self]; $Create => [] _ Create[ViewerTools.GetSelectionContents[]]; $MoveViewer => MoveViewer[SelectedViewer[], SelectedDeskTop[], self, TRUE]; $CopyViewer => MoveViewer[SelectedViewer[], SelectedDeskTop[], self, FALSE]; $Rename => Rename[self, ViewerTools.GetSelectionContents[]]; $OpenEntry => OpenEntry[FetchEntryData[self]]; $GrowEntry => OpenEntry[e: FetchEntryData[self], grow: TRUE]; $RemoveViewer => RemoveViewer[self, SelectedViewer[]]; $SelectEntry => InputFocus.SetInputFocus[self]; $Write => WriteFile[self]; $Read => { file: Rope.ROPE _ ViewerTools.GetSelectionContents[]; IF file.Length[] = 0 THEN file _ self.name; ReadFile[file]; }; ENDCASE; ENDLOOP; }; SelectedViewer: PROC RETURNS[viewer: Viewer] = { viewer _ ViewerTools.GetSelectedViewer[]; IF viewer = NIL THEN RETURN; IF viewer.class.flavor = $Button THEN { entry: Entry _ FetchEntryData[viewer]; IF entry # NIL THEN viewer _ entry.viewer}; IF viewer = NIL THEN RETURN; WHILE viewer.parent # NIL DO viewer _ viewer.parent; ENDLOOP; }; SelectedDeskTop: PROC RETURNS[DeskTop] = { viewer: Viewer _ ViewerTools.GetSelectedViewer[]; WHILE viewer # NIL DO IF viewer.class.flavor = $DeskTop THEN RETURN[viewer]; viewer _ viewer.parent; ENDLOOP; RETURN[NIL]; }; <<******* basic procedures ******* >> Create: PUBLIC PROC[name: Rope.ROPE _ NIL] RETURNS[desktop: Viewer] = { FindDeskTop: ViewerOps.EnumProc = { IF v.class.flavor = $DeskTop AND Rope.Equal[v.name, name] THEN {desktop _ v; RETURN[TRUE]}}; IF name.Length[] = 0 THEN name _ "Desktop"; ViewerOps.EnumerateViewers[FindDeskTop]; IF current = NIL THEN current _ ViewerOps.CreateViewer[flavor: $DeskTop, info: [name: "Default", column: right, iconic: TRUE]]; IF desktop = NIL THEN desktop _ ViewerOps.CreateViewer[flavor: $DeskTop, info: [name: name, column: right, iconic: TRUE]]; IF desktop.offDeskTop THEN ViewerOps.ChangeColumn[desktop, right]; }; Rename: PUBLIC PROC[desktop: DeskTop, name: Rope.ROPE] = { data: DeskTopData; desktop.name _ name; data _ FetchDeskTopData[desktop]; data.iconBitmap _ NIL; -- so new bitmap will be created ViewerOps.PaintViewer[desktop, caption]; }; FlyTo: PUBLIC PROC[desktop: Viewer] = { data: DeskTopData; OpenEntries: ViewerOps.EnumProc = {OpenEntry[FetchEntryData[v], TRUE]}; SetHeights: ViewerOps.EnumProc = {SetHeight[v]}; OpenDesktops: ViewerOps.EnumProc = {IF v.offDeskTop AND v.class.flavor = $DeskTop THEN ViewerOps.ChangeColumn[v, right]}; IF desktop = NIL OR desktop.class.flavor # $DeskTop THEN RETURN; IF desktop = current THEN {ViewerOps.PaintEverything[]; RETURN}; IF current # NIL THEN { -- save the screen in the current desktop temp: DeskTop _ current; current _ NIL; -- allows additions to be made to temp temp.menu _ menu; RecordScreenState[temp]; ClearScreen[offDeskTop: TRUE]; }; current _ desktop; -- make current desktop.menu _ NIL; -- standard for current desktop data _ FetchDeskTopData[desktop]; IF data.openLeftWidth # ViewerSpecs.openLeftWidth OR data.openBottomY # ViewerSpecs.openBottomY THEN ViewerOps.MoveBoundary[data.openLeftWidth, data.openBottomY]; ViewerOps.EnumerateChildren[desktop, OpenEntries]; ViewerOps.EnumerateChildren[desktop, SetHeights]; ViewerOps.EnumerateViewers[OpenDesktops]; [] _ FlushEntries[desktop, save, TRUE]; data.iconBitmap _ NIL; -- so new bitmap will be created ViewerOps.PaintEverything[]; }; RecordScreenState: PROC[desktop: Viewer] = { AddToDesktop: ViewerOps.EnumProc = { IF v.column = static THEN RETURN; AddEntry[desktop, GetEntry[NIL, v].entry]}; data: DeskTopData _ FetchDeskTopData[desktop]; data.openLeftWidth _ ViewerSpecs.openLeftWidth; data.openBottomY _ ViewerSpecs.openBottomY; [] _ FlushEntries[desktop, save, TRUE]; ViewerOps.EnumerateViewers[AddToDesktop]; data.iconBitmap _ NIL; -- so new bitmap will be created }; ClearScreen: PROC[offDeskTop: BOOL _ TRUE] = { CloseViewer: ViewerOps.EnumProc = { IF v.column = static THEN RETURN; IF ~v.iconic THEN ViewerOps.CloseViewer[v, FALSE]; IF offDeskTop THEN ViewerOps.ChangeColumn[v, static]}; -- move it off the screen ViewerOps.EnumerateViewers[CloseViewer]; }; ClearIcons: PUBLIC PROC = { LockedClear: PROC = { AddIconic: ViewerOps.EnumProc = { IF v.iconic AND ~v.offDeskTop AND UneditedDocument[v] THEN { AddEntry[MRUCache, GetEntry[NIL, v].entry, firstButton, 25]; ViewerOps.DestroyViewer[v]}}; ViewerOps.EnumerateViewers[AddIconic]; }; IF MRUCache = NIL OR MRUCache.destroyed THEN { MRUCache _ Containers.Create[info: [name: "MRU Cache", column: right, iconic: TRUE]]; ViewerOps.AddProp[MRUCache, $DeskTopData, NEW[DeskTopDataRec _ []]]}; IF MRUCache.offDeskTop THEN ViewerOps.ChangeColumn[MRUCache, right]; InputFocus.SetInputFocus[]; ViewerLocks.CallUnderViewerTreeLock[LockedClear ! ViewerLocks.Wedged => CONTINUE]; }; EnumerateEntries: PUBLIC PROC[desktop: DeskTop, proc: EnumProc] = { child: Viewer _ desktop.child; WHILE child # NIL DO next: Viewer _ child.sibling; IF ~proc[FetchEntryData[child]] THEN EXIT; child _ next; ENDLOOP; }; SetHeight: PROC[button: Viewer] = INLINE { e: Entry _ FetchEntryData[button]; IF e.iconic THEN RETURN; e.viewer.position _ e.position; e.viewer.openHeight _ e.height; }; UneditedDocument: PROC[v: Viewer] RETURNS[BOOL] = INLINE { RETURN[v # NIL AND v.class.flavor = $Text AND ~v.newVersion AND ~v.newFile]}; continue: BOOL _ TRUE; ReadFile: PUBLIC PROC [filename: Rope.ROPE] = { ENABLE { UNWIND => NULL; FS.Error => { MessageWindow.Append[Rope.Cat["Error reading desktop file: ", error.explanation], TRUE]; MessageWindow.Blink[]; IF continue THEN CONTINUE}; ANY => { MessageWindow.Append[Rope.Cat["Error reading desktop file: ", filename], TRUE]; MessageWindow.Blink[]; IF continue THEN CONTINUE}}; entry: Entry; desktop: DeskTop; stream: IO.STREAM; entries: LIST OF Entry; IF filename.Find["."] < 0 THEN filename _ Rope.Cat[filename, ".desktop"]; stream _ FS.StreamOpen[filename, read]; WHILE ~stream.EndOf[] DO token: Rope.ROPE _ stream.GetTokenRope[! IO.EndOfStream => EXIT].token; SELECT TRUE FROM Rope.Equal[token, "desktop"] => { desktop _ Create[stream.GetTokenRope[].token]; [] _ FlushEntries[desktop, save, TRUE]}; Rope.Equal[token, "viewer"] => { IF desktop = NIL THEN LOOP; entry _ NEW[EntryRec _ []]; entries _ CONS[entry, entries]; -- use a list to reverse the order [] _ stream.GetChar[]; -- get rid of ':. entry.name _ stream.GetRopeLiteral[]}; Rope.Equal[token, "class"] => IF entry # NIL THEN { [] _ stream.GetChar[]; -- get rid of ':. entry.flavor _ stream.GetAtom[]}; Rope.Equal[token, "file"] => IF entry # NIL THEN { [] _ stream.GetChar[]; -- get rid of ':. entry.backingFile _ stream.GetRopeLiteral[]}; Rope.Equal[token, "column"] => IF entry # NIL THEN { column: Rope.ROPE _ stream.GetTokenRope[].token; SELECT TRUE FROM Rope.Equal[column, "left"] => entry.column _ left; Rope.Equal[column, "right"] => entry.column _ right; Rope.Equal[column, "color"] => entry.column _ color; Rope.Equal[column, "color"] => entry.column _ static; ENDCASE}; Rope.Equal[token, "iconic"] => IF entry # NIL THEN { [] _ stream.GetChar[]; -- get rid of ':. entry.iconic _ stream.GetBool[]}; Rope.Equal[token, "position"] => IF entry # NIL THEN { [] _ stream.GetChar[]; -- get rid of ':. entry.position _ stream.GetInt[]}; Rope.Equal[token, "openHeight"] => IF entry # NIL THEN { [] _ stream.GetChar[]; -- get rid of ':. entry.height _ stream.GetInt[]}; ENDCASE => NULL; ENDLOOP; FOR entries _ entries, entries.rest WHILE entries # NIL DO AddEntry[desktop, entries.first]; ENDLOOP; FetchDeskTopData[desktop].iconBitmap _ NIL; ViewerOps.PaintViewer[desktop, all]; stream.Close[]; }; WriteFile: PUBLIC PROC [desktop: DeskTop, filename: Rope.ROPE _ NIL] = { stream: IO.STREAM; IF filename.Length[] = 0 THEN filename _ desktop.name; IF filename.Find["."] < 0 THEN filename _ Rope.Cat[filename, ".desktop"]; stream _ FS.StreamOpen[filename, create]; stream.PutF["[desktop: %g,\n", IO.rope[desktop.name]]; FOR child: Viewer _ desktop.child, child.sibling WHILE child # NIL DO entry: Entry _ FetchEntryData[child]; stream.PutF["[viewer: ""%g"",\n\tclass: $%g, file: ""%g"",\n", IO.rope[entry.name], IO.atom[entry.flavor], IO.rope[entry.backingFile]]; SELECT entry.column FROM left => stream.Put[IO.rope["\tcolumn: left, "]]; right => stream.Put[IO.rope["\tcolumn: right, "]]; color => stream.Put[IO.rope["\tcolumn: color, "]]; static => stream.Put[IO.rope["\tcolumn: static, "]]; ENDCASE => ERROR; stream.PutF["iconic: %g, position: %g, openHeight: %g]", IO.bool[entry.iconic], IO.int[entry.position], IO.int[entry.height]]; stream.Put[IO.rope[IF child.sibling = NIL THEN "];\n\n" ELSE ",\n"]]; ENDLOOP; stream.Close[]; }; <<******* detail procedures *******>> <<>> MoveViewer: PUBLIC PROC[viewer: Viewer, old, new: DeskTop, remove: BOOL] = { IF viewer = NIL THEN RETURN; IF old = new AND remove THEN RETURN; IF old = NIL AND new = NIL THEN RETURN; IF old # NIL AND old.class.flavor # $DeskTop THEN RETURN; IF new # NIL AND new.class.flavor # $DeskTop THEN RETURN; IF new = current THEN RETURN; -- cannot add viewers WHILE viewer.parent # NIL DO viewer _ viewer.parent; ENDLOOP; IF new # NIL THEN {AddEntry[new, GetEntry[old, viewer].entry]; ViewerOps.PaintViewer[new, all]} ELSE OpenEntry[GetEntry[old, viewer].entry]; IF remove THEN IF old # NIL THEN RemoveViewer[old, viewer] ELSE { -- remove from screen IF ~viewer.iconic THEN { ViewerOps.CloseViewer[viewer, FALSE]; ViewerOps.ComputeColumn[viewer.column]}; ViewerOps.ChangeColumn[viewer, static]}; }; RemoveViewer: PROC[desktop: DeskTop, viewer: Viewer] = { entry: Entry; button: Buttons.Button; MoveUp: ViewerOps.EnumProc = {IF v.wy > button.wy THEN ViewerOps.MoveViewer[v, v.wx, v.wy-buttonHeight, v.ww, v.wh, FALSE]}; button _ GetEntry[desktop, viewer].button; IF button = NIL THEN RETURN; entry _ FetchEntryData[button]; IF entry.viewer # NIL AND ~entry.viewer.destroyed AND entry.viewer.offDeskTop THEN ViewerOps.ChangeColumn[entry.viewer, entry.column]; ViewerOps.EnumerateChildren[desktop, MoveUp]; ViewerOps.DestroyViewer[button, FALSE]; ViewerOps.PaintViewer[desktop, all]; }; GetEntry: PROC[desktop, v: Viewer] RETURNS[entry: Entry, button: Buttons.Button] = { IF desktop = NIL THEN { IF v.props = NIL THEN ViewerOps.AddProp[v, $DesktopDummy, $PlaceHolder]; entry _ NEW[EntryRec _ [viewer: v, name: v.name, backingFile: v.file, flavor: v.class.flavor, props: v.props, column: v.column, iconic: v.iconic, position: v.position, height: v.openHeight, wy: v.wy, wh: v.wh]]; RETURN[entry, NIL]}; FOR child: Viewer _ desktop.child, child.sibling WHILE child # NIL DO entry: Entry _ FetchEntryData[child]; IF entry # NIL AND entry.viewer = v THEN RETURN[entry, child]; ENDLOOP; RETURN[NIL, NIL]; }; AddEntry: PUBLIC PROC[desktop: Viewer, entry: Entry, height, limit: INTEGER _ -1] = { title: Rope.ROPE; button: Buttons.Button; IF desktop = current THEN RETURN; -- cannot add viewers [] _ desktop.class.scroll[desktop, thumb, 0]; title _ Rope.Cat[Atom.GetPName[entry.flavor], ": ", entry.name]; <> IF height < 0 THEN FOR v: Viewer _ desktop.child, v.sibling WHILE v # NIL DO new: Entry _ FetchEntryData[v]; IF new.viewer = entry.viewer AND new.viewer # NIL THEN { ViewerOps.AddProp[v, $Entry, entry]; IF ~Rope.Equal[title, v.name] THEN Buttons.ReLabel[v, title]; RETURN}; IF Rope.Compare[v.name, title] # greater THEN height _ MAX[height, v.wy + buttonHeight]; ENDLOOP; IF height < 0 THEN height _ firstButton; <> FOR v: Viewer _ desktop.child, v.sibling WHILE v # NIL DO IF limit > 0 AND v.wy > limit*buttonHeight THEN {ViewerOps.DestroyViewer[v]; LOOP}; IF v.wy >= height THEN ViewerOps.MoveViewer[v, v.wx, v.wy + buttonHeight, v.ww, v.wh, FALSE]; ENDLOOP; <> button _ Buttons.Create[ info: [name: title, parent: desktop, wy: height], proc: MyButtonProc, paint: FALSE]; ViewerOps.AddProp[button, $Entry, entry]; }; OpenEntry: PROC[e: Entry, all, grow: BOOL _ FALSE] = { iconic: BOOL _ all AND e.iconic; moved: BOOL _ FALSE; IF e.viewer = NIL OR e.viewer.destroyed THEN { -- find or create a replacement e.viewer _ ViewerOps.FindViewer[e.name]; IF e.viewer # NIL AND e.viewer.class.flavor # e.flavor THEN e.viewer _ NIL; IF e.viewer = NIL THEN { e.viewer _ ViewerOps.CreateViewer[flavor: e.flavor, info: [name: e.name, file: e.backingFile, iconic: iconic, column: e.column], paint: ~all]; FOR props: Atom.PropList _ e.props, props.rest WHILE props # NIL DO IF props.first.key = $DesktopDummy THEN LOOP; ViewerOps.AddProp[e.viewer, NARROW[props.first.key], props.first.val]; ENDLOOP; IF e.viewer.props = NIL THEN ViewerOps.AddProp[e.viewer, $DesktopDummy, $PlaceHolder]; moved _ TRUE}}; <> IF e.viewer.offDeskTop THEN { ViewerOps.ChangeColumn[e.viewer, e.column]; IF ~all THEN iconic _ e.iconic}; IF iconic AND ~e.viewer.iconic THEN { ViewerOps.CloseViewer[e.viewer, ~all]; moved _ TRUE}; IF e.column # e.viewer.column THEN { -- close viewer to avoid repainting the world IF ~e.viewer.iconic AND all THEN ViewerOps.CloseViewer[e.viewer, ~all]; ViewerOps.ChangeColumn[e.viewer, e.column]; moved _ TRUE}; IF ~iconic AND e.viewer.iconic THEN { ViewerOps.OpenIcon[icon: e.viewer, paint: ~all]; moved _ TRUE}; <> IF grow THEN {ViewerOps.GrowViewer[e.viewer]; moved _ TRUE}; <> IF ~all AND ~moved THEN ViewerOps.PaintViewer[e.viewer, all]; }; FetchEntryData: PROC[button: Buttons.Button] RETURNS[entry: Entry] = INLINE { RETURN[NARROW[ViewerOps.FetchProp[button, $Entry]]]}; FetchDeskTopData: PROC[desktop: DeskTop] RETURNS[entry: DeskTopData] = INLINE { RETURN[NARROW[ViewerOps.FetchProp[desktop, $DeskTopData]]]}; FlushEntries: ViewerEvents.EventProc = { <<[viewer: Viewer, event: ViewerEvent, before: BOOL]>> list: LIST OF Viewer; MakeList: ViewerOps.EnumProc = {list _ CONS[v, list]}; IF event = destroy AND viewer = current THEN current _ NIL; ViewerOps.EnumerateChildren[viewer, MakeList]; FOR list _ list, list.rest WHILE list # NIL DO entry: Entry = FetchEntryData[list.first]; IF entry # NIL AND entry.viewer # NIL AND ~entry.viewer.destroyed AND entry.viewer.offDeskTop THEN ViewerOps.ChangeColumn[entry.viewer, entry.column]; ViewerOps.DestroyViewer[list.first, FALSE]; ENDLOOP; }; Init[]; END.