DeskTopsImpl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Last Edited by: Maxwell, December 7, 1983 9:28 am
Doug Wyatt, April 15, 1985 5:50:45 pm PST
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;
create menu
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];
create new class
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];
invert if selected
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: BOOLFALSE;
context: Imager.Context;
erase icon area
data.iconBitmap ← ImagerBackdoor.NewBitmap[w, h];
context ← ImagerBackdoor.BitmapContext[data.iconBitmap];
Imager.SetColor[context, Imager.white];
Imager.MaskRectangleI[context, 0, 0, w, h];
draw outlines of inner boxes
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;
paint in grey areas
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];
draw name
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.ROPENIL] 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: BOOLTRUE] = {
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: BOOLTRUE;
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.ROPENIL] = {
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];
sort by name if no height is given
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;
move the rest of the buttons down. delete buttons that fall off the bottom (limit).
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;
add the new button
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: BOOLFALSE] = {
iconic: BOOL ← all AND e.iconic;
moved: BOOLFALSE;
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}};
move the viewer to where it should be
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 all AND ~e.viewer.iconic THEN {ViewerOps.TopViewer[e.viewer, ~all]; moved ← TRUE};
IF grow THEN {ViewerOps.GrowViewer[e.viewer]; moved ← TRUE};
if the viewer hasn't been moved, repaint it
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.