DIRECTORY
Atom USING [GetPName, PropList],
Buttons USING [Button, ButtonProc, Create, ReLabel, SetDisplayStyle],
Commander USING [CommandProc, Register],
Containers USING [Create],
Desktops USING [DeskTop, Entry, EntryRec, EnumProc],
FS USING [Error, StreamOpen],
Imager USING [black, Color, Context, Font, MaskRectangleI, SetColor, SetFont, SetXYI, ShowRope, white],
ImagerBackdoor USING [Bitmap, BitmapContext, DrawBits, invert, MakeStipple, NewBitmap],
InputFocus USING [SetInputFocus],
IO USING [atom, bool, Close, EndOf, EndOfStream, GetAtom, GetBool, GetChar, GetInt, GetRopeLiteral, GetTokenRope, IDProc, int, Put, PutF, RIS, rope, STREAM],
List USING [Assoc, PutAssoc],
Menus USING [AppendMenuEntry, CreateEntry, CreateMenu, Menu, MenuProc],
MessageWindow USING [Append, Blink],
ProcessProps USING [GetProp],
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, List, Menus, MessageWindow, ProcessProps, Rope, VFonts, ViewerEvents, ViewerLocks, ViewerOps, ViewerSpecs, ViewerTools
EXPORTS Desktops
SHARES ViewerOps
= {
DeskTopData: TYPE = REF DeskTopDataRec;
DeskTopDataRec:
TYPE =
RECORD[
iconBitmap: ImagerBackdoor.Bitmap ← NIL, -- icon bitmap
openLeftWidth: INTEGER ← 0,
openBottomY: INTEGER ← 0
];
current: ViewerClasses.Viewer;
MRUCache: ViewerClasses.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[ViewerClasses.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.", $CreateDeskTop];
Commander.Register["ReadDesktop", DoIt, "Reads a desktop from a file on the disk.", $ReadDeskTop];
Commander.Register["WriteDesktop", DoIt, "Overwrites a desktop file on the disk.", $WriteDeskTop];
};
DoIt: Commander.CommandProc = {
token: Rope.ROPE;
stream: IO.STREAM;
stream ← IO.RIS[cmd.commandLine];
token ← stream.GetTokenRope[IO.IDProc].token;
stream.Close[];
SELECT cmd^.procData^.clientData
FROM
$ReadDeskTop => ReadFile[token];
$WriteDeskTop => 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: ViewerClasses.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;
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: ViewerClasses.Viewer ← self.child, child.sibling
WHILE child #
NIL
DO
entry: Desktops.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: ViewerClasses.Viewer] = {
viewer ← ViewerTools.GetSelectedViewer[];
IF viewer = NIL THEN RETURN;
IF viewer.class.flavor = $Button
THEN {
entry: Desktops.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[Desktops.DeskTop] = {
viewer: ViewerClasses.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: ViewerClasses.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]];
desktop.props ← List.PutAssoc[$DeskTopWorkingDirectory, ProcessProps.GetProp[$WorkingDirectory], desktop.props];
};
IF desktop.offDeskTop THEN ViewerOps.ChangeColumn[desktop, right];
};
Rename:
PUBLIC
PROC[desktop: Desktops.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: ViewerClasses.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: Desktops.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: ViewerClasses.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: Desktops.DeskTop, proc: Desktops.EnumProc] = {
child: ViewerClasses.Viewer ← desktop.child;
WHILE child #
NIL
DO
next: ViewerClasses.Viewer ← child.sibling;
IF ~proc[FetchEntryData[child]] THEN EXIT;
child ← next;
ENDLOOP;
};
SetHeight:
PROC[button: ViewerClasses.Viewer] =
INLINE {
e: Desktops.Entry ← FetchEntryData[button];
IF e.iconic THEN RETURN;
e.viewer.position ← e.position;
e.viewer.openHeight ← e.height;
};
UneditedDocument:
PROC[v: ViewerClasses.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: Desktops.Entry;
desktop: Desktops.DeskTop;
stream: IO.STREAM;
entries: LIST OF Desktops.Entry;
IF filename.Find["."] < 0 THEN filename ← Rope.Cat[filename, ".desktop"];
stream ← FS.StreamOpen[fileName: filename, accessOptions: read, wDir: NARROW[ProcessProps.GetProp[$WorkingDirectory], Rope.ROPE]];
WHILE ~stream.EndOf[]
DO
token: Rope.ROPE ← stream.GetTokenRope[! IO.EndOfStream => EXIT].token;
SELECT
TRUE
FROM
Rope.Equal[token, "desktop"] => {
desktop ← Create[stream.GetTokenRope[IO.IDProc].token];
[] ← FlushEntries[desktop, save, TRUE]
};
Rope.Equal[token, "viewer"] => {
IF desktop = NIL THEN LOOP;
entry ← NEW[Desktops.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: Desktops.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: filename, accessOptions: create, wDir: NARROW[List.Assoc[$DeskTopWorkingDirectory, desktop.props], Rope.ROPE]];
stream.PutF["[desktop: %g,\n", IO.rope[desktop.name]];
FOR child: ViewerClasses.Viewer ← desktop.child, child.sibling
WHILE child #
NIL
DO
entry: Desktops.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]];
IF child.sibling # NIL THEN
stream.Put[IO.rope[",\n"]];
ENDLOOP;
stream.Put[IO.rope["];\n\n"]];
stream.Close[];
};
detail procedures
MoveViewer:
PUBLIC
PROC[viewer: ViewerClasses.Viewer, old, new: Desktops.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: Desktops.DeskTop, viewer: ViewerClasses.Viewer] = {
entry: Desktops.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: ViewerClasses.Viewer]
RETURNS[entry: Desktops.Entry, button: Buttons.Button] = {
IF desktop =
NIL
THEN {
IF v.props = NIL THEN ViewerOps.AddProp[v, $DesktopDummy, $PlaceHolder];
entry ← NEW[Desktops.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: ViewerClasses.Viewer ← desktop.child, child.sibling
WHILE child #
NIL
DO
entry: Desktops.Entry ← FetchEntryData[child];
IF entry # NIL AND entry.viewer = v THEN RETURN[entry, child];
ENDLOOP;
RETURN[NIL, NIL];
};
AddEntry:
PUBLIC
PROC[desktop: ViewerClasses.Viewer, entry: Desktops.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: ViewerClasses.Viewer ← desktop.child, v.sibling
WHILE v #
NIL
DO
new: Desktops.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: ViewerClasses.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: Desktops.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
}
};
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: Desktops.Entry] =
INLINE {
RETURN[NARROW[ViewerOps.FetchProp[button, $Entry]]]
};
FetchDeskTopData:
PROC[desktop: Desktops.DeskTop]
RETURNS[entry: DeskTopData] =
INLINE {
RETURN[NARROW[ViewerOps.FetchProp[desktop, $DeskTopData]]]
};
FlushEntries: ViewerEvents.EventProc = {
[viewer: Viewer, event: ViewerEvent, before: BOOL]
list: LIST OF ViewerClasses.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: Desktops.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[];
}.