SlateWidgetImpl.mesa
Copyright Ó 1990, 1992, 1993 by Xerox Corporation. All rights reserved.
Created by Christian Jacobi, November 2, 1990 1:04:32 pm PST
Weiser, September 3, 1992 8:10 pm PDT
Theimer, December 17, 1992 3:15 pm PST
Christian Jacobi, August 24, 1993 5:13 pm PDT
This used to be a real simple small demo program to show the power of BitmapWidgets. User whishes and MPad performance hacks made it not quite simple anymore.
DIRECTORY
Ascii,
Char,
Commander,
CommanderOps,
ConvertUnsafe,
FanoutStream,
ForkOps,
Identification,
Imager,
ImagerBackdoor,
ImagerBox,
ImagerColor,
ImagerFont,
ImagerPath,
ImagerSample,
IO,
KeySymsSun,
KeySymsOSF,
KeySymsHP,
KeySymsKB,
PropList,
Real,
Rope,
RuntimeError,
SF,
SharedCell,
SlateControls,
SlateIO,
Slate,
Xl,
XlAscii,
XlBitmap,
XlCutBuffers,
XTk,
XTkBitmapScroller,
XTkBitmapWidgetsExtras,
XTkSharedCell,
XTkPopUps,
XTkLabels,
XTkWidgets;
SlateWidgetImpl:
CEDAR
MONITOR
IMPORTS Char, Commander, CommanderOps, ConvertUnsafe, FanoutStream, ForkOps, Identification, Imager, ImagerBackdoor, ImagerColor, ImagerFont, ImagerPath, ImagerSample, IO, PropList, Real, Rope, RuntimeError, SharedCell, SlateControls, Slate, SlateIO, Xl, XlAscii, XlBitmap, XlCutBuffers, XTk, XTkBitmapScroller, XTkBitmapWidgetsExtras, XTkSharedCell, XTkLabels, XTkPopUps, XTkWidgets
EXPORTS Slate ~
BEGIN OPEN Slate;
Widget: TYPE = XTk.Widget;
varyingFlag: XTk.WidgetFlagKey ~ wf6;
Data: TYPE = REF DataRec;
DataRec:
TYPE =
RECORD [
page: Page ← NIL,
scrollPos: Xl.Point ¬ [0, 0],
followTextCell: SharedCell.Cell ¬ NIL,
followMode: REF FollowSpec ¬ NIL,
motionType: INT ¬ 1,
motionT: Xl.TimeStamp ¬ Xl.currentTime,
motionP: Xl.Point ¬ [0, 0],
instance: Instance ¬ NIL,
controlPanel: ControlPanel ¬ NIL,
tq: Xl.TQ ¬ NIL,
log: IO.STREAM,
context: Imager.Context ¬ NIL,
currentFontData: REF FontData ¬ NIL,
buttonMode: ATOM ¬ $normal,
buttonModeFancy: BOOL ¬ FALSE,
hasStart: BOOL ¬ FALSE,
startP: Xl.Point ¬ [0, 0],
directPaintGC: Xl.GContext ¬ NIL,
directEraseGC: Xl.GContext ¬ NIL,
directErasePixmap: Xl.Pixmap ¬ Xl.nullPixmap,
delayMore: BOOL ¬ FALSE,
delayedPaintList: LIST OF DelayedPaintRec ¬ NIL,
bitmapSize: Xl.Size ¬ [0, 0],
eraserWidth: INT ¬ 3,
eraseMinimum: REAL ¬ 2.5,
eraseTrajectory: Imager.Trajectory ¬ NIL,
mode: ATOM ¬ NIL,
sname, usedPageIndicator, bitmapScroller, realBitmapWidget, pagesCont, shellWidget, fonts, colorWidget, eraserWidthWidget, invertMouseWidget: Widget,
lineHeight: REAL ¬ 10,
textOfLine: Rope.ROPE ¬ NIL,
inputfocusX: REAL ¬ 0,
inputfocusY: REAL ¬ 0,
imageMode: ImageModeRep ¬ []
];
DelayedPaintRec:
TYPE =
RECORD [
p1, p2: Xl.Point
];
FontData:
TYPE =
RECORD [
presentName: Rope.ROPE,
realName: Rope.ROPE,
scale: REAL
];
ColorSpec:
TYPE =
RECORD [
color: Imager.Color,
name: Rope.ROPE
];
ImageModeRep:
TYPE =
RECORD [
color: REF ColorSpec ¬ NIL,
font: Imager.Font ¬ NIL,
thickness: INT ¬ 1,
requiredLineHeight: INT ¬ 20
];
FollowSpec:
TYPE =
RECORD [
follow: BOOL,
name: Rope.ROPE
];
keyForPage: REF INT ~ NEW [INT];
eraseColor: Imager.Color ~ Imager.MakeGray[0.125];
fontChoiceList: XTkPopUps.ChoiceList ¬ NIL;
colorChoiceList: XTkPopUps.ChoiceList ¬ NIL;
followChoiceList: XTkPopUps.ChoiceList ¬ NIL;
defaultFontData: REF FontData;
defaultColorData: REF ColorSpec;
defaultFollowData: REF FollowSpec;
nowhereBM: XlBitmap.Bitmap ¬ XlBitmap.Create[[1, 1], 1, FALSE];
nowhereContext: Imager.Context ¬ XlBitmap.CreateContext[nowhereBM];
ResetBM:
PROC [d: Data, newBM: XlBitmap.Bitmap] = {
bm: XlBitmap.Bitmap ¬ IF newBM=NIL THEN nowhereBM ELSE newBM;
box: SF.Box ~ XlBitmap.GetBox[bm];
sz: Xl.Size ~ [box.max.f-box.min.f, box.max.s-box.min.s];
context: Imager.Context;
XTkBitmapScroller.SetBitmap[d.bitmapScroller, bm];
IF ImagerSample.GetBitsPerSample[XlBitmap.GetSM[bm]]>1
THEN {
implWidget: Widget ¬ XTkBitmapScroller.GetImplWidget[d.bitmapScroller];
XTkBitmapWidgetsExtras.FollowColors[implWidget, bm];
};
XTkTIP.ChangePseudoHeight[d.instance.surface, sz.height];
context ¬ XlBitmap.CreateContext[bm];
Imager.ClipRectangle[context, [1, 1, sz.width-2, sz.height-2]];
Imager.SetStrokeEnd[context, round];
Imager.SetStrokeJoint[context, round];
d.bitmapSize ¬ sz;
d.context ¬ context;
UseThickness[d];
UseFont[d];
UseColor[d];
};
InitFonts:
PROC [] = {
Look at /imagerfonts/xerox/tiogafonts/ to find fonts
currentList: XTkPopUps.ChoiceList ¬ NIL;
FontEntry:
PROC [realName: Rope.
ROPE, scale:
REAL, short: Rope.
ROPE]
RETURNS [fd:
REF FontData] = {
fd ¬
NEW[FontData ¬ [
presentName: short,
realName: realName,
scale: scale
]];
currentList ¬
CONS[
[short, fd, NIL, "Font on type in", SetFontButtonHit], currentList
];
};
FontSubMenu:
PROC [familyName: Rope.
ROPE] = {
fontChoiceList ¬
CONS[
[familyName, NIL, currentList, "Use this font family"], fontChoiceList
];
currentList ¬ NIL;
};
defaultFontData ¬
NEW[FontData ¬ [
presentName: "Tioga10 ",
realName: "Xerox/TiogaFonts/Tioga10",
scale: 1.0
]];
[] ¬ FontEntry["Xerox/TiogaFonts/Tioga10", 1.0, "Tioga10"];
[] ¬ FontEntry["Xerox/TiogaFonts/Tioga10", 1.5, "Tioga10 x 1.5"];
[] ¬ FontEntry["Xerox/TiogaFonts/Tioga10", 2.0, "Tioga10 x 2"];
[] ¬ FontEntry["Xerox/TiogaFonts/Tioga10", 3.0, "Tioga10 x 3"];
[] ¬ FontEntry["Xerox/TiogaFonts/Tioga10", 4.0, "Tioga10 x 4"];
[] ¬ FontEntry["Xerox/TiogaFonts/Tioga10", 6.0, "Tioga10 x 6"];
[] ¬ FontEntry["Xerox/TiogaFonts/Tioga10", 8.0, "Tioga10 x 8"];
FontSubMenu["Tioga"];
[] ¬ FontEntry["Xerox/TiogaFonts/Gacha10", 1.0, "Gacha10"];
[] ¬ FontEntry["Xerox/TiogaFonts/Gacha10", 2.0, "Gacha10 x 2"];
[] ¬ FontEntry["Xerox/TiogaFonts/Gacha10", 3.0, "Gacha10 x 3"];
[] ¬ FontEntry["Xerox/TiogaFonts/Gacha10", 4.0, "Gacha10 x 4"];
[] ¬ FontEntry["Xerox/TiogaFonts/Gacha10", 6.0, "Gacha10 x 6"];
[] ¬ FontEntry["Xerox/TiogaFonts/Gacha10", 8.0, "Gacha10 x 8"];
FontSubMenu["Gacha"];
[] ¬ FontEntry["Xerox/TiogaFonts/Helvetica10", 1.0, "Helvetica10"];
[] ¬ FontEntry["Xerox/TiogaFonts/Helvetica14", 1.0, "Helvetica14"];
defaultFontData ¬ FontEntry["Xerox/TiogaFonts/Helvetica18", 1.0, "Helvetica18"];
[] ¬ FontEntry["Xerox/TiogaFonts/Helvetica18", 1.5, "Helvetica18 x 1.5"];
[] ¬ FontEntry["Xerox/TiogaFonts/Helvetica18", 2.0, "Helvetica18 x 2.0"];
[] ¬ FontEntry["Xerox/TiogaFonts/Helvetica18", 3.0, "Helvetica18 x 3.0"];
[] ¬ FontEntry["Xerox/TiogaFonts/Helvetica18", 4.0, "Helvetica18 x 4.0"];
FontSubMenu["Helvetica"];
[] ¬ FontEntry["Xerox/TiogaFonts/Helvetica10i", 1.0, "Helvetica10 italic"];
[] ¬ FontEntry["Xerox/TiogaFonts/Helvetica14i", 1.0, "Helvetica14 italic"];
[] ¬ FontEntry["Xerox/TiogaFonts/Helvetica18i", 1.0, "Helvetica18 italic"];
[] ¬ FontEntry["Xerox/TiogaFonts/Helvetica18i", 1.5, "Helvetica18 italic x 1.5"];
[] ¬ FontEntry["Xerox/TiogaFonts/Helvetica18i", 2.0, "Helvetica18 italic x 2.0"];
[] ¬ FontEntry["Xerox/TiogaFonts/Helvetica18i", 3.0, "Helvetica18 italic x 3.0"];
[] ¬ FontEntry["Xerox/TiogaFonts/Helvetica18i", 4.0, "Helvetica18 italic x 4.0"];
FontSubMenu["Helvetica italic"];
[] ¬ FontEntry["Xerox/TiogaFonts/Helvetica10b", 1.0, "Helvetica10 bold"];
[] ¬ FontEntry["Xerox/TiogaFonts/Helvetica14b", 1.0, "Helvetica14 bold"];
[] ¬ FontEntry["Xerox/TiogaFonts/Helvetica18b", 1.0, "Helvetica18 bold"];
[] ¬ FontEntry["Xerox/TiogaFonts/Helvetica18b", 1.5, "Helvetica18 bold x 1.5"];
[] ¬ FontEntry["Xerox/TiogaFonts/Helvetica18b", 2.0, "Helvetica18 bold x 2.0"];
[] ¬ FontEntry["Xerox/TiogaFonts/Helvetica18b", 3.0, "Helvetica18 bold x 3.0"];
[] ¬ FontEntry["Xerox/TiogaFonts/Helvetica18b", 4.0, "Helvetica18 bold x 4.0"];
FontSubMenu["Helvetica bold"];
[] ¬ FontEntry["Xerox/TiogaFonts/oldenglish10", 1.0, "Oldenglish10"];
[] ¬ FontEntry["Xerox/TiogaFonts/math10", 1.0, "Math10"];
[] ¬ FontEntry["Xerox/TiogaFonts/timesroman10", 1.0, "TimesRoman10"];
[] ¬ FontEntry["Xerox/TiogaFonts/hippo10", 1.0, "Hippo10"];
[] ¬ FontEntry["Xerox/TiogaFonts/hebrew14", 1.0, "Hebrew14"];
[] ¬ FontEntry["Xerox/TiogaFonts/cyrillic12", 1.0, "Cyrillic12"];
[] ¬ FontEntry["Xerox/TiogaFonts/cream12", 1.0, "Cream12"];
FontSubMenu["Others"];
};
InitColors:
PROC [] = {
ColorEntry:
PROC [name: Rope.
ROPE, color: Imager.Color]
RETURNS [cs:
REF ColorSpec] = {
cs ¬
NEW[ColorSpec ¬ [
color: color,
name: name
]];
colorChoiceList ¬
CONS[
[name, cs, NIL, "Color for painting", SetColorButtonHit], colorChoiceList
];
};
[] ¬ ColorEntry["invert", ImagerBackdoor.invert];
[] ¬ ColorEntry["H", ImagerColor.ColorFromHighlight[[0.5, 0.5]]];
[] ¬ ColorEntry["B", ImagerColor.ColorFromRGB[[0.0, 0.0, 1.0]]];
[] ¬ ColorEntry["G", ImagerColor.ColorFromRGB[[0.0, 1.0, 0.0]]];
[] ¬ ColorEntry["R", ImagerColor.ColorFromRGB[[1.0, 0.0, 0.0]]];
[] ¬ ColorEntry["white", Imager.white];
[] ¬ ColorEntry["1/8", Imager.MakeGray[0.125]];
[] ¬ ColorEntry["2/8", Imager.MakeGray[0.25]];
[] ¬ ColorEntry["3/8", Imager.MakeGray[0.375]];
[] ¬ ColorEntry["4/8", Imager.MakeGray[0.5]];
[] ¬ ColorEntry["5/8", Imager.MakeGray[0.625]];
[] ¬ ColorEntry["6/8", Imager.MakeGray[0.75]];
[] ¬ ColorEntry["7/8", Imager.MakeGray[0.875]];
defaultColorData ¬ ColorEntry["black", Imager.black];
};
InitFollows:
PROC [] = {
FollowEntry:
PROC [name: Rope.
ROPE, follow:
BOOL, help: Rope.
ROPE]
RETURNS [f:
REF FollowSpec] = {
f ← NEW[FollowSpec ← [follow: follow, name: name]];
followChoiceList ¬
CONS[
[image: name, key: f, impl: NIL, help: help, notify: SetFollowButtonHit], followChoiceList
];
};
defaultFollowData ¬ FollowEntry["Follow: independent", FALSE, "Everyone can see a different page."];
[] ¬ FollowEntry["Follow: synchronous", TRUE, "Everyone sees the page I see."];
};
ControlPanel: TYPE = REF ControlPanelRec;
ControlPanelRec:
TYPE =
RECORD [
d: Data,
controlShell: Widget ¬ NIL
];
SwapControlPanel:
ENTRY
PROC [d: Data, c: ControlPanel]
RETURNS [old: ControlPanel ¬
NIL] = {
IF d#
NIL
THEN {
old ¬ d.controlPanel;
d.controlPanel ¬ c
};
};
SetControlPanel:
PROC [d: Data, c: ControlPanel] = {
c ¬ SwapControlPanel[d, c];
IF c#
NIL
THEN {
controlShell: Widget ¬ c.controlShell;
IF controlShell#NIL THEN XTkWidgets.DestroyShell[controlShell]
};
};
MotionHistoryHit: XTkWidgets.ButtonHitProcType = {
d: Data ~ NARROW[registerData];
IF d.motionType=0
THEN {
d.motionType ¬ 1;
XTkWidgets.SetText[widget, "Motion: events "];
}
ELSE {
d.motionType ¬ 0;
XTkWidgets.SetText[widget, "Motion: history "];
};
};
RenameSessionHit: XTkWidgets.ButtonHitProcType = {
instance: Slate.Instance ~ NARROW[registerData];
session: Session ¬ instance.session;
nameWidget: Widget ¬ NARROW[callData];
name: Rope.ROPE ¬ XTkWidgets.GetText[nameWidget];
IF session#NIL THEN SetSessionName[session, name];
};
SetFollowButtonHit: XTkWidgets.ButtonHitProcType = {
instance: Slate.Instance ~ NARROW[registerData];
f: REF FollowSpec ~ NARROW[callData];
d: Data ~ NARROW[instance.uiPrivate];
ChooseFollow[d, f];
};
CreateControlPanelWidget:
PROC [d: Data] = {
c: ControlPanel ¬ NEW[ControlPanelRec ¬ [d: d]];
shellWidget: Widget ¬ c.controlShell ¬ XTkWidgets.CreateShell[
rootTQ: d.tq,
className: $SlateControl,
windowHeader: "Slate Control",
packageName: "SlateControl",
shortName: "Slate Control"
];
--
--log
logWidget: Widget ¬ XTkWidgets.CreateStreamWidget[widgetSpec: [geometry: XTk.G[-1, 60]]];
--
--pages
newPageS: Widget ~ XTkWidgets.CreateButton[
text: "NewPage", hitProc: NewPageHit, registerData: d, tq: d.tq
];
pageSizeBut: Widget ~ XTkWidgets.CreateToggle[
choices: LIST[["(large)", $large], ["(small)", $small]] ,
hitProc: PageSizeHit, registerData: d, tq: d.tq
];
clearPage: Widget ~ XTkWidgets.CreateButton[
text: "ClearPage ", hitProc: ClearPageButtonHit, registerData: d, tq: d.tq
];
deletePage: Widget ~ XTkWidgets.CreateButton[
text: "DeletePage ", hitProc: DeletePageButtonHit, registerData: d, tq: d.tq
];
deleteAllPages: Widget ~ XTkWidgets.CreateButton[
text: "DeleteAllPages ", hitProc: DeleteAllPagesButtonHit, registerData: d, tq: d.tq
];
name: Widget ¬ XTkWidgets.CreateLabel[];
topControlsCont: Widget ~ XTkWidgets.CreateXStack[[],
LIST[newPageS, pageSizeBut, clearPage, deletePage, deleteAllPages, name]
];
--
fancyControlsCont: Widget ~ SlateControls.MakeRowWidget[d.instance, $Main];
--
createColorMenu: XTkPopUps.WidgetCreateClosure ¬ XTkPopUps.WidgetCreateClosureFromChoiceList[list: colorChoiceList, defaultNotify: SetColorButtonHit, notifyTQ: d.tq];
colorChoiceWidget: Widget ¬ d.colorWidget ¬ XTkPopUps.CreatePopUpButton[
text: " ...color... ", createMenu: createColorMenu, registerData: d
];
createFontMenu: XTkPopUps.WidgetCreateClosure ¬ XTkPopUps.WidgetCreateClosureFromChoiceList[list: fontChoiceList, defaultNotify: SetFontButtonHit, notifyTQ: d.tq];
fonts: Widget ¬ d.fonts ¬ XTkPopUps.CreatePopUpButton[
text: " ...font... ", createMenu: createFontMenu, registerData: d
];
motion: Widget ~ XTkWidgets.CreateButton[
text: "Motion: events ", hitProc: MotionHistoryHit, registerData: d
];
controls1Cont: Widget ¬ XTkWidgets.CreateXStack[[],
LIST[colorChoiceWidget, fonts, motion]
];
--
--
topYCont: Widget ← XTkWidgets.CreateYStack[[],
LIST[XTkWidgets.HRule[], logWidget, XTkWidgets.HRule[], topControlsCont, controls1Cont, fancyControlsCont]
];
XTkSharedCell.BindText[d.instance.session.nameCell, name];
XTk.SetWidgetFlag[logWidget, varyingFlag];
XTkWidgets.SetShellChild[c.controlShell, topYCont];
XTkWidgets.BindScreenShell[c.controlShell, d.shellWidget.connection ! Xl.connectionNotCreated => {
IO.PutRope[d.instance.log, Rope.Concat["Failed: ", why.reason]];
GOTO oops
}];
XTkWidgets.BindStream[logWidget, d.instance.log];
SetControlPanel[d, c];
TrackColor[d];
TrackThickness[d];
TrackPageSize[d, pageSizeBut];
XTkWidgets.RealizeShell[c.controlShell];
EXITS oops => {};
};
CreateControlPanelButtonHit: XTkWidgets.ButtonHitProcType = {
d: Data ~ NARROW[registerData];
CreateControlPanelWidget[d];
};
CreateSlateWidget:
PROC []
RETURNS [Data] = {
d: Data ¬
NEW[DataRec ¬ [
context: nowhereContext,
followTextCell: SharedCell.Create[initial: defaultFollowData.name]
]];
tq: Xl.TQ ¬ d.tq ¬ Xl.CreateTQ[order: XTk.rootLockingOrder];
shell: Widget ¬ d.shellWidget ¬ XTkWidgets.CreateShell[
rootTQ: tq,
className: $Slate,
windowHeader: Rope.Concat["Slate on ", Identification.Self[]],
packageName: "Slate",
shortName: "Slate"
];
bitmapScroller: Widget ¬ d.bitmapScroller ¬ XTkBitmapScroller.CreateBitmapScroller[scrollTQ: tq, scrollData: d, scrolledCallBack: ScrolledCallBack];
widthLabelWidget: Widget ~ XTkWidgets.CreateLabel[[], "W:"];
widthChoiceWidget: Widget ~ XTkWidgets.CreateChoices[
choices:
LIST [
["1", NEW[INT ¬ 1]],
["2", NEW[INT ¬ 2]],
["3", NEW[INT ¬ 3]],
["4", NEW[INT ¬ 4]],
["6", NEW[INT ¬ 6]],
["10", NEW[INT ¬ 10]],
["15", NEW[INT ¬ 15]],
["20", NEW[INT ¬ 20]],
["25", NEW[INT ¬ 25]],
["30", NEW[INT ¬ 30]]
],
hitProc: ThicknessButtonHit, registerData: d
];
sname: Widget ¬ d.sname ¬ XTkWidgets.CreateLabel[];
eraserWidthWidget: Widget ¬ d.eraserWidthWidget ¬ XTkWidgets.CreateLabel[[], EraserExplanation[d]];
invertMouseWidget: Widget ¬ d.invertMouseWidget ¬ XTkWidgets.CreateButton[
text: "...", hitProc: InvertButtonsButtonHit, registerData: d, tq: tq
];
createControlPanel: Widget ~ XTkWidgets.CreateButton[
text: "Panel ", hitProc: CreateControlPanelButtonHit, registerData: d, tq: tq
];
newPage: Widget ~ XTkWidgets.CreateButton[
text: "Page", hitProc: NewPageHit, registerData: d, tq: tq
];
safe: Widget ~ XTkWidgets.CreateButton[
text: "Safe", hitProc: SafeHit, registerData: d, tq: tq
];
controls1Cont: Widget ¬ XTkWidgets.CreateXStack[[],
LIST[createControlPanel, widthLabelWidget, widthChoiceWidget, eraserWidthWidget, invertMouseWidget, newPage, safe, sname]
];
pagesCont: Widget ¬ d.pagesCont ¬ XTkWidgets.CreateYStack[[geometry: [size: [width: 20, height: Xl.dontUse]]], NIL];
mainAreaCont: Widget ¬ XTkWidgets.CreateXStack[[],
LIST[pagesCont, XTkWidgets.VRule[], bitmapScroller]
];
topYCont: Widget ← XTkWidgets.CreateYStack[[geometry: [pos: [1, 1], size: [600, 400], borderWidth: Xl.dontUse]],
LIST[XTkWidgets.HRule[], controls1Cont, XTkWidgets.HRule[], mainAreaCont]
];
bmw: Widget ← d.realBitmapWidget ¬ XTkBitmapScroller.GetImplWidget[bitmapScroller];
XTk.PutWidgetProp[bmw, $BitmapBorder, NEW[INT ¬ 1]];
XTk.PutWidgetProp[bmw, $OverheadCost, NEW[INT ¬ 1]];
XTk.PutWidgetProp[bmw, $ServerQueueLimit, NEW[INT ¬ 4]];
XTkWidgets.SetShellChild[shell, topYCont];
XTk.AddPermanentMatch[bmw, [proc: TipEvent, handles: Xl.CreateEventFilter[keyPress, buttonPress, buttonRelease, motionNotify], tq: tq, data: d], [buttonMotion: TRUE, buttonRelease: TRUE, buttonPress: TRUE, keyRelease: TRUE, keyPress: TRUE]];
XTk.AddPermanentMatch[bmw, [proc: ClientMessageEvent, handles: Xl.CreateEventFilter[clientMessage], tq: tq, data: d]];
XTk.RegisterNotifier[shell, XTk.postWidgetDestructionKey, ShellDestroyed, d];
XTkWidgets.SetFocusMethod[shell: shell, focusProtocol: true, inputHint: false]; --Globally active
XTk.RegisterNotifier[shell, XTk.preWindowCreationKey, HackWindowCreation, d];
TrackInvertButtons[d];
ForkOps.ForkPeriodically[501, PeriodicalFork, d];
RETURN [d];
};
HackWindowCreation: XTk.WidgetNotifyProc = {
d: Data ~ NARROW[registerData];
c: Xl.Connection ¬ widget.connection;
screen: Xl.Screen ¬ widget.screenDepth.screen;
pGC, eGC: Xl.GContext;
--direct paining
d.directPaintGC ¬ pGC ¬ Xl.MakeGContext[c, widget.window];
Xl.SetGCForeground[pGC, screen.blackPixel];
Xl.SetGCBackground[pGC, screen.whitePixel];
Xl.SetGCFunction[pGC, copy];
Xl.SetGCCapStyle[pGC, round];
Xl.SetGCJoinStyle[pGC, round];
--direct erasing
d.directEraseGC ¬ eGC ¬ Xl.MakeGContext[c, widget.window];
Xl.SetGCFunction[gc: eGC, function: copy];
Xl.SetGCForeground[eGC, screen.blackPixel];
Xl.SetGCBackground[eGC, screen.whitePixel];
d.directErasePixmap ¬ Xl.CreatePixmap[c, screen.root.drawable, [4, 4], widget.screenDepth.depth];
TRUSTED {
Xl.PutImage[c: c, drawable: d.directErasePixmap.drawable, gc: eGC, size: [4, 4], dest: [0, 0], base: LOOPHOLE[@stippleSpace[0]], offx: 0, offy: 0, scanLineBytes: 4, bitsPerPixel: 1];
};
Xl.SetGCTile[eGC, d.directErasePixmap];
Xl.SetGCFillStyle[eGC, tiled];
Xl.SetGCLineWidth[eGC, d.eraserWidth];
Xl.SetGCCapStyle[eGC, round];
Xl.SetGCJoinStyle[eGC, round];
};
stippleSpace:
REF
ARRAY [0..4)
OF
CARD32 =
NEW[ARRAY [0..4) OF CARD32 ¬ [088888888H, 0, 022222222H, 0]];
PeriodicalFork:
PROC [data:
REF] = {
d: Data ~ NARROW[data];
IF ~d.delayMore
THEN {Xl.Enqueue[d.tq, TQPaintDelayed, d, NIL]; d.delayMore ¬ TRUE}
ELSE d.delayMore ¬ d.delayedPaintList=NIL;
};
TQPaintDelayed: Xl.EventProcType = {
d: Data ~ NARROW[clientData];
PaintDelayed[d];
};
PaintDelayed: <<d.tq>>
PROC [d: Data] = {
hl: LIST OF DelayedPaintRec ¬ d.delayedPaintList;
d.delayedPaintList ¬ NIL;
FOR l:
LIST
OF DelayedPaintRec ¬ hl, l.rest
WHILE l#
NIL
DO
Imager.MaskVector[d.context,
[Cx[d, l.first.p1.x], Cy[d, l.first.p1.y]],
[Cx[d, l.first.p2.x], Cy[d, l.first.p2.y]]
];
ENDLOOP;
};
ShellDestroyed: XTk.WidgetNotifyProc = {
d: Data ~ NARROW[registerData];
SetControlPanel[d, NIL];
DestroyInstance[d.instance]; --is idempotent
ForkOps.Stop[PeriodicalFork, d]
};
ContinuePaint: <<tip notifier>>
PROC [d: Data, p: Xl.Point] =
INLINE {
IF d.mode=$paint
THEN {
IF ~d.hasStart THEN SetStart[d, p];
--also paints the first null vector to draw a nice point
IF d.imageMode.color.color=Imager.black
THEN {
--This is much faster for slow connections
d.delayMore ¬ TRUE;
d.delayedPaintList ¬ CONS[[d.startP, p], d.delayedPaintList];
Xl.DrawLine[d.realBitmapWidget.connection, d.realBitmapWidget.window, d.startP, p, d.directPaintGC];
Xl.Flush[d.realBitmapWidget.connection];
}
ELSE {
Imager.MaskVector[d.context,
[Cx[d, d.startP.x], Cy[d, d.startP.y]],
[Cx[d, p.x], Cy[d, p.y]]
];
};
d.startP ¬ p;
};
};
ContinueErase: <<tip notifier>>
PROC [d: Data, p: Xl.Point] =
INLINE {
IF d.mode=$erase
AND d.eraseTrajectory#
NIL
THEN {
IF ~d.hasStart THEN SetStart[d, p];
d.eraseTrajectory ¬ ImagerPath.LineTo[d.eraseTrajectory, [x: Cx[d, p.x], y: Cy[d, p.y]]];
--also paints the first null vector to draw a nice circle on button down
Xl.DrawLine[d.realBitmapWidget.connection, d.realBitmapWidget.window, d.startP, p, d.directEraseGC];
Xl.Flush[d.realBitmapWidget.connection];
d.startP ¬ p;
};
};
ContinueAny: <<tip notifier>>
PROC [d: Data, p: Xl.Point] = {
SELECT d.mode
FROM
$paint => ContinuePaint[d, p];
$erase => ContinueErase[d, p];
ENDCASE => {};
};
EndPaint: <<tip notifier>>
PROC [d: Data, p: Xl.Point] =
INLINE {
IF d.hasStart AND d.mode=$paint THEN ContinuePaint[d, p];
d.hasStart ¬ FALSE;
d.mode ¬ NIL;
};
EndErase: <<tip notifier>>
PROC [d: Data, p: Xl.Point] = {
IF d.hasStart AND d.mode=$erase THEN ContinueErase[d, p];
IF d.eraseTrajectory#
NIL
THEN {
action:
PROC [] = {
Imager.SetColor[d.context, Imager.white];
Imager.SetStrokeWidth[d.context, d.eraserWidth];
Imager.MaskStrokeTrajectory[d.context, d.eraseTrajectory];
};
Imager.DoSave[d.context, action];
d.eraseTrajectory ¬ NIL;
};
IF d.mode=$erase
THEN {
Imager.SetStrokeWidth[d.context, d.imageMode.thickness];
Imager.SetColor[d.context, d.imageMode.color.color];
};
d.mode ¬ NIL;
d.hasStart ¬ FALSE;
};
EndAny: <<tip notifier>>
PROC [d: Data, p: Xl.Point] = {
SELECT d.mode
FROM
$paint => EndPaint[d, p];
$erase => EndErase[d, p];
ENDCASE => {};
d.mode ¬ NIL;
};
PaintSetStart: <<tip notifier>>
PROC [d: Data, p: Xl.Point] =
INLINE {
IF d.mode=$erase THEN EndErase[d, p];
d.mode ¬ $paint;
SetStart[d, p];
};
EraseSetStart: <<tip notifier>>
PROC [d: Data, p: Xl.Point] =
INLINE {
PaintDelayed[d];
IF d.mode#$erase
AND ~d.hasStart
THEN {
SetStart[d, p];
d.mode ¬ $erase;
d.eraseTrajectory ¬ ImagerPath.MoveTo[[x: Cx[d, p.x], y: Cy[d, p.y]]];
Imager.SetStrokeWidth[d.context, d.eraserWidth];
Imager.SetColor[d.context, eraseColor];
ContinueErase[d, p];
};
};
SetStart:
PROC [d: Data, p: Xl.Point] =
INLINE {
d.startP ¬ p;
d.hasStart ¬ TRUE;
};
GridAndSetFocus: <<tip notifier>>
PROC [d: Data] = {
d.textOfLine ¬ NIL;
d.inputfocusY ¬ d.inputfocusY/4*4; --gridding y to make alignment easier
Imager.SetXY[d.context, [d.inputfocusX, d.inputfocusY]];
};
AdvanceLine: <<tip notifier>>
PROC [d: Data, leftX:
BOOL ¬
FALSE] = {
IF leftX THEN d.inputfocusX ¬ 5;
d.inputfocusY ¬ d.inputfocusY - d.lineHeight;
GridAndSetFocus[d];
};
PaintRope: <<tip notifier>>
PROC [d: Data, r: Rope.
ROPE] = {
ENABLE RuntimeError.UNCAUGHT => GOTO Oops;
BackSpaceChar:
PROC [d: Data] = {
leng: INT ~ Rope.Length[d.textOfLine];
IF leng>0
THEN {
BackWardsProc: ImagerFont.XStringProc = {
charAction[Char.Make[set: 0, code: ORD[ch]]];
};
ch: CHAR ~ Rope.Fetch[d.textOfLine, leng-1];
d.textOfLine ¬ Rope.Substr[d.textOfLine, 0, leng-1];
Imager.SetColor[d.context, Imager.white];
Imager.ShowBackward[d.context, BackWardsProc];
Imager.SetColor[d.context, d.imageMode.color.color];
};
};
length: INT ~ Rope.Length[r];
nextPos: INT ¬ 0;
d.hasStart ¬ FALSE;
IF d.mode=$erase THEN EndErase[d, d.startP];
d.lineHeight ¬ d.imageMode.requiredLineHeight;
Imager.SetFont[d.context, d.imageMode.font];
IF length=1
THEN {
SELECT Rope.Fetch[r, 0]
FROM
Ascii.BS, Ascii.DEL => {BackSpaceChar[d]; RETURN};
ENDCASE => {}
};
DO
crPos: INT ~ Rope.SkipTo[s: r, pos: nextPos, skip: "\l\r"];
IF crPos>nextPos
THEN {
piece: Rope.ROPE ¬ Rope.Substr[r, nextPos, crPos-nextPos];
Imager.ShowRope[d.context, piece];
d.textOfLine ¬ Rope.Concat[d.textOfLine, piece];
};
nextPos ¬ crPos+1;
IF nextPos>=length THEN RETURN;
AdvanceLine[d, FALSE];
ENDLOOP;
EXITS Oops => {};
};
SetInputFocus: <<tip notifier>>
PROC [d: Data, x, y:
REAL] = {
i: Instance ¬ d.instance;
XTkWidgets.SetFocus[shell: i.shell, time: Xl.currentTime, child: i.surface];
d.inputfocusX ¬ x;
d.inputfocusY ¬ y;
GridAndSetFocus[d];
};
ScrolledCallBack: XTkBitmapScroller.ScrolledCallBackProc = {
d: Data ¬ NARROW[data];
PaintDelayed[d];
d.scrollPos ¬ pos;
XTkTIP.ChangeScrollPos[d.istance.surface, [pos.x, pos.y]];
};
SetFontButtonHit: XTkWidgets.ButtonHitProcType = {
d: Data ~ NARROW[registerData];
fd: REF FontData ~ NARROW[callData];
ChooseFont[d, fd];
};
SetColorButtonHit: XTkWidgets.ButtonHitProcType = {
d: Data ~ NARROW[registerData];
cs: REF ColorSpec ~ NARROW[callData];
ChooseColor[d, cs];
};
ChooseColor:
PROC [d: Data, cs:
REF ColorSpec] = {
d.imageMode.color ¬ cs;
IF d.mode#$erase
AND d.context#
NIL
THEN
Imager.SetColor[d.context, d.imageMode.color.color];
TrackColor[d];
};
UseColor:
PROC [d: Data] = {
IF d.mode#$erase THEN Imager.SetColor[d.context, d.imageMode.color.color];
};
TrackColor:
PROC [d: Data] = {
colorWidget: Widget ¬ d.colorWidget;
cs: REF ColorSpec ¬ d.imageMode.color;
IF colorWidget#
NIL
AND cs#
NIL
THEN
XTkWidgets.SetText[colorWidget, Rope.Cat["Color: ", cs.name, " "]];
};
ChooseFollow:
PROC [d: Data, f:
REF FollowSpec] = {
page: Slate.Page ¬ Slate.ActivePage[d.instance.session];
d.followMode ← f;
SharedCell.SetContents[d.followTextCell, f.name];
IF d.followMode.follow
AND LocalActivePage[d]#page
AND page#
NIL
THEN
LocalActivatePage[d, page]
};
MessageHit: XTkWidgets.ButtonHitProcType = {
instance: Slate.Instance ~ NARROW[registerData];
tField: Widget ~ NARROW[callData];
message: Rope.ROPE ¬ XTkWidgets.GetText[tField];
CallGlobalForAllInstancesOfSession[session: instance.session, event: $Message, callData: Rope.Cat[Xl.ServerName[widget.connection], "> ", message, "\n"]]
};
IsColorWidget:
PROC [w: Widget]
RETURNS [
BOOL] = {
shell: Widget ¬ XTk.RootWidget[w];
RETURN [XTk.GetWidgetProp[shell, $Color]#NIL]
};
ClientMessageEvent: Xl.EventProcType = {
d: Data ~ NARROW[clientData];
SELECT event.type
FROM
Xl.EventCode.clientMessage => {
cme: Xl.ClientMessageEvent ~ NARROW[event];
a: Xl.XAtom ¬ Xl.MakeAtom[cme.connection, "PARC←Handwriting"];
IF cme.typeAtom=a
AND cme.w[0]=Xl.AtomId[a]
THEN {
i: INT32 ~ LOOPHOLE[cme.w[1]];
stuffKey: INT32 ~ -1;
nextKey: INT32 ~ -3;
SELECT i
FROM
stuffKey => {
text: Rope.ROPE ¬ XlCutBuffers.Get[cme.connection];
PaintRope[d, text];
};
nextKey => {
AdvanceLine[d, TRUE]
};
ENDCASE => {};
};
};
ENDCASE => {};
};
Cx:
PROC [d: Data, x:
INT]
RETURNS [
INT] =
INLINE {
Given an X window coordinate, returns an Imager coordinate
RETURN [x-d.scrollPos.x]
};
Cy:
PROC [d: Data, y:
INT]
RETURNS [
INT] =
INLINE {
Given an X window coordinate, returns an Imager coordinate
RETURN [d.bitmapSize.height-y+d.scrollPos.y]
};
listOfSpecials: LIST OF Xl.KeySym ~ LIST[KeySymsSun.Paste, KeySymsOSF.Paste, KeySymsHP.Paste];
TipEvent: Xl.EventProcType = {
d: Data ~ NARROW[clientData];
Motion:
PROC [d: Data, stopT: Xl.TimeStamp, stopP: Xl.Point, stop:
BOOL] = {
i: Instance ~ d.instance;
cnt: NAT ¬ 0;
tc: REF Xl.TimeCoords ¬ NIL;
IF d.motionType=0
THEN {
tc ¬ Xl.GetMotionEvents[c: i.surface.connection, window: i.surface.window, start: d.motionT, stop: stopT];
cnt ¬ tc.size
};
IF cnt=0
THEN {
IF stop
THEN EndAny[d, stopP]
ELSE ContinueAny[d, stopP];
}
ELSE {
FOR i:
NAT
IN [0..cnt-1)
DO
ContinueAny[d, tc[i].p];
ENDLOOP;
BEGIN
l: NAT ~ cnt-1;
IF stop
THEN EndAny[d, tc[l].p]
ELSE ContinueAny[d, tc[l].p];
END;
};
d.motionT ¬ stopT;
d.motionP ¬ stopP;
};
SELECT event.type
FROM
Xl.EventCode.buttonPress => {
e: Xl.ButtonPressEvent ~ NARROW[event];
paint: BOOL ¬ e.button=1;
IF e.button=2
THEN {
SetInputFocus[d, Cx[d, e.pos.x], Cy[d, e.pos.y]];
RETURN
};
IF (e.state.button1
OR e.state.button2
OR e.state.button3)
AND d.buttonMode
#$focus
THEN {
Only the first button is looked at...
RETURN;
};
SELECT d.buttonMode
FROM
$normal => {};
$inverted => paint ¬ NOT paint;
$focus => {
IF (e.state.button1
OR e.state.button2
OR e.state.button3)
THEN {
insert: Rope.ROPE ¬ XlCutBuffers.Get[e.connection];
PaintRope[d, insert];
}
ELSE SetInputFocus[d, Cx[d, e.pos.x], Cy[d, e.pos.y]];
RETURN
};
ENDCASE => RETURN;
d.motionT ¬ e.timeStamp;
d.motionP ¬ e.pos;
--Now we either paint, or, erase
IF paint
THEN PaintSetStart[d, e.pos]
ELSE EraseSetStart[d, e.pos];
};
Xl.EventCode.buttonRelease => {
e: Xl.ButtonReleaseEvent ~ NARROW[event];
cnt: INT ¬ ORD[e.state.button1] + ORD[e.state.button2] + ORD[e.state.button3];
stop: BOOL ¬ cnt<=1; --only stop when last button is released
Motion[d, e.timeStamp, e.pos, stop];
IF stop
THEN {
d.buttonModeFancy ¬ FALSE;
IF d.buttonMode=$focus
THEN {
d.buttonMode ¬ $normal;
TrackInvertButtons[d];
};
};
};
Xl.EventCode.motionNotify => {
e: Xl.MotionNotifyEvent ~ NARROW[event];
Motion[d, e.timeStamp, e.pos, FALSE];
};
Xl.EventCode.keyPress => {
char: CHAR; keysym: Xl.KeySym; matched: Xl.KeySym; isModifier: BOOL;
e: Xl.KeyPressEvent ~ NARROW[event];
[char: char, keysym: keysym, matched: matched, isModifier: isModifier] ¬ XlAscii.Convert[e.connection, e.keyCode, e.state, listOfSpecials];
IF isModifier THEN RETURN;
SELECT matched
FROM
KeySymsSun.Paste, KeySymsOSF.Paste, KeySymsHP.Paste => {
insert: Rope.ROPE ¬ XlCutBuffers.Get[e.connection];
PaintRope[d, insert];
};
ENDCASE => {
SELECT char
FROM
'\l => AdvanceLine[d, TRUE];
'\r => AdvanceLine[d, FALSE];
ENDCASE => PaintRope[d, Rope.FromChar[char]];
};
};
ENDCASE => {};
InstallBitmap: <<unmonitored: widget initialization>>
PROC [d: Data, bitmap: XlBitmap.Bitmap] = {
<<NOTE: tip notifier not yet active>>
ChooseThickness[d, 1];
ChooseFont[d, defaultFontData];
ChooseColor[d, defaultColorData];
ChooseFollow[d, defaultFollowData];
ResetBM[d, bitmap];
d.inputfocusY ¬ d.bitmapSize.height;
AdvanceLine[d, TRUE];
};
InvertButtonsButtonHit: XTkWidgets.ButtonHitProcType = {
d: Data ~ NARROW[registerData];
SELECT d.buttonMode
FROM
$normal => d.buttonMode ¬ IF d.buttonModeFancy THEN $focus ELSE $inverted;
$inverted => d.buttonMode ¬ IF d.buttonModeFancy THEN $focus ELSE $normal;
ENDCASE => d.buttonMode ¬ $normal;
d.buttonModeFancy ¬ NOT d.buttonModeFancy;
TrackInvertButtons[d];
};
TrackInvertButtons:
PROC [d: Data] = {
invertMouseWidget: Widget ¬ d.invertMouseWidget;
IF invertMouseWidget#
NIL
THEN {
text: Rope.ROPE;
SELECT d.buttonMode
FROM
$normal => text ¬ "painting";
$inverted => text ¬ "erasing";
$focus => text ¬ "focus";
ENDCASE => {};
XTkWidgets.SetText[invertMouseWidget, text]
};
};
ThicknessButtonHit: XTkWidgets.ButtonHitProcType = {
d: Data ~ NARROW[registerData];
width: INT ~ NARROW[callData, REF INT];
ChooseThickness[d, width];
};
ClearPageButtonHit: <<tq>> XTkWidgets.ButtonHitProcType = {
d: Data ~ NARROW[registerData];
page: Page ¬ LocalActivePage[d];
IF page=
NIL
THEN IO.PutRope[d.log, "No active page\n"]
ELSE {
ImagerSample.Clear[XlBitmap.GetSM[page.activeArea]];
XlBitmap.Refresh[page.activeArea];
}
};
Bind:
PROC [d: Data, i: Instance] = {
d.instance ¬ i;
i.uiPrivate ¬ d;
d.log ¬ i.log;
i.shell ¬ d.shellWidget;
i.surface ¬ d.realBitmapWidget;
InstallBitmap[d, i.session.activePage.activeArea];
InitPages[d];
XTkSharedCell.BindText[d.instance.session.nameCell, d.sname];
Slate.CallGlobalEvent[i, i.session, $UICreated];
PageHasBeenActivated[instance: i, session: i.session, callData: NIL, registerData: NIL, event: NIL];
};
DoSharedTool:
PROC [session: Session, server:
REF, log:
IO.
STREAM] = {
instance: Instance ¬ NewInstance[session];
d: Data ← CreateSlateWidget[];
XTkWidgets.BindScreenShell[d.shellWidget, server ! Xl.connectionNotCreated => {
IO.PutRope[log, Rope.Concat["Failed: ", why.reason]];
GOTO oops
}];
Bind[d, instance];
XTkWidgets.RealizeShell[d.shellWidget];
IO.PutRope[log, " Slate tool created\n"];
EXITS oops => {};
};
GetTQ:
PROC [instance: Instance]
RETURNS [Xl.
TQ] = {
RETURN [NARROW[instance.uiPrivate, Data].tq]
};
NewInstanceTool:
PUBLIC PROC [session: Session, server:
REF]
RETURNS [instance: Instance] =
BEGIN
d: Data ← CreateSlateWidget[];
XTkWidgets.BindScreenShell[d.shellWidget, server ! Xl.connectionNotCreated => {
IO.PutRope[session.debugLog, Rope.Concat["Failed: ", why.reason]];
GOTO oops
}];
instance ¬ NewInstance[session];
Bind[d, instance];
XTkWidgets.RealizeShell[d.shellWidget];
IO.PutRope[session.debugLog, " Slate tool created\n"];
EXITS oops => {};
END;
SelectSessionButtonHit: XTkWidgets.ButtonHitProcType = {
instance: Slate.Instance ~ NARROW[registerData];
session: Session ¬ instance.session;
globalLastSession ¬ session;
};
DeleteAllPagesButtonHit: XTkWidgets.ButtonHitProcType = {
d: Data ~ NARROW[registerData];
bpp: NAT ¬ IF IsColorWidget[d.shellWidget] THEN 8 ELSE 1;
session: Session ¬ d.instance.session;
newPage: Page ¬ NewPage[session: session, bpp: bpp];
EachPage: PageProc = {
IF page#newPage AND page#NIL THEN RemovePage[page];
};
ActivatePage[newPage];
[] ¬ EnumeratePages[session, EachPage];
};
DeletePageButtonHit: XTkWidgets.ButtonHitProcType = {
d: Data ~ NARROW[registerData];
session: Session ¬ d.instance.session;
delete: Page ¬ LocalActivePage[d];
pages: LIST OF Page ¬ CurrentListOfPages[session];
IF pages#
NIL
THEN {
nextPage: Page ¬ pages.first;
IF pages.rest=
NIL
THEN {
IO.PutRope[d.log, "must keep one page\n"];
RETURN
};
FOR list:
LIST
OF Page ¬ pages, list.rest
WHILE list#
NIL DO
IF list.first=delete
THEN {
IF list.rest#NIL THEN {nextPage ¬ list.rest.first};
EXIT;
};
ENDLOOP;
IF nextPage=delete THEN nextPage ¬ NIL;
ActivatePage[nextPage];
IF delete#NIL THEN RemovePage[delete];
};
};
EraserExplanation:
PROC [d: Data]
RETURNS [Rope.
ROPE] = {
RETURN [IO.PutFR1[" (%4g)", IO.int[-d.eraserWidth]]]
};
CreateSharedToolMenu: XTkPopUps.CreateWidgetProc = {
in: REF InstName ~ NARROW[closureData];
menu: Widget ~ XTkWidgets.CreateYStack[];
OneChild:
PROC [text: Rope.
ROPE, key:
REF ¬
NIL] = {
child: Widget ¬ XTkWidgets.CreateButton[text: text, hitProc: SharedToolButtonHit, registerData: in, callData: key];
XTkWidgets.AppendChild[menu, child];
};
OneChild["Same X server", NIL];
OneChild["Using name field", $sharedWidget];
RETURN [menu];
};
SharedToolButtonHit: XTkWidgets.ButtonHitProcType = {
server: REF ANY;
in: REF InstName ~ NARROW[registerData];
WITH callData
SELECT
FROM
a:
ATOM =>{
server ¬ XTkWidgets.GetText[in.name];
};
ENDCASE => server ¬ widget.connection;
DoSharedTool[in.instance.session, server, in.instance.log];
};
ChooseThickness:
PROC [d: Data, width:
INT] = {
d.imageMode.thickness ¬ width ¬ MIN[MAX[width, 1], 200];
d.eraserWidth ¬ MAX[width, 3]*3 + 3;
d.eraseMinimum ¬ d.eraserWidth/10+1.5;
UseThickness[d];
TrackThickness[d];
};
UseThickness:
PROC [d: Data] = {
IF d.context#
NIL
THEN {
Imager.SetStrokeWidth[d.context, d.imageMode.thickness];
};
IF d.directPaintGC#
NIL
THEN {
Xl.SetGCLineWidth[d.directPaintGC, d.imageMode.thickness];
Xl.SetGCLineWidth[d.directEraseGC, d.eraserWidth];
};
};
TrackThickness:
PROC [d: Data] = {
eraserWidthWidget: Widget ¬ d.eraserWidthWidget;
IF eraserWidthWidget#
NIL
THEN
XTkWidgets.SetText[eraserWidthWidget, EraserExplanation[d]]
};
ChooseFont:
PROC [d: Data, fd:
REF FontData] = {
ENABLE Imager.Error => GOTO Oops;
font: Imager.Font; e: ImagerFont.Extents;
d.currentFontData ¬ fd;
font ¬ ImagerFont.Scale[ImagerFont.Find[fd.realName, substituteQuietly], fd.scale];
e ¬ ImagerFont.FontBoundingBox[font];
d.imageMode.font ← font;
d.imageMode.requiredLineHeight ¬ Real.Ceiling[e.descent + e.ascent + e.ascent / 5];
TrackFont[d];
EXITS Oops => {};
};
UseFont:
PROC [d: Data] = {
};
TrackFont:
PROC [d: Data] = {
fd: REF FontData ¬ d.currentFontData;
fonts: Widget ¬ d.fonts;
IF fonts#
NIL
THEN
XTkWidgets.SetText[fonts, Rope.Cat["Font: ", fd.presentName, " "]];
};
ParticipantsButtonHit: XTkWidgets.ButtonHitProcType = {
instance: Slate.Instance ~ NARROW[registerData];
log: IO.STREAM ~ instance.log;
Each: InstanceProc = {
WITH instance.uiPrivate
SELECT
FROM
d: Data => {
c: Xl.Connection ← d.shellWidget.connection;
IF Xl.Alive[c]
THEN {
IO.PutRope[log, Xl.ServerName[c]];
IO.PutRope[log, " "]
}
ELSE {IO.PutRope[log, "dead-server "]}
};
ENDCASE => {IO.PutRope[log, "unknown-server "]}
};
[] ← EnumerateInstances[instance.session, Each];
IO.PutRope[log, "\n"];
};
ActivatePageButtonHit: XTkWidgets.ButtonHitProcType = {
d: Data ~ NARROW[registerData];
page: Page ~ NARROW[callData];
IF page#
NIL
THEN
IF d.followMode.follow
THEN ActivatePage[page]
ELSE LocalActivatePage[d, page];
};
NewPageWithSize:
PROC [d: Data, sz:
SF.Vec ¬ [0, 0]] = {
bpp: NAT ¬ IF IsColorWidget[d.shellWidget] THEN 8 ELSE 1;
page: Page ¬ Slate.NewPage[session: d.instance.session, bpp: bpp, sz: sz];
IF d.followMode.follow
THEN ActivatePage[page]
ELSE LocalActivatePage[d, page];
};
PageSizeHit: XTkWidgets.ButtonHitProcType = {
d: Data ~ NARROW[registerData];
[] ¬ PropList.PutProp[d.instance.properties, $PageSize, callData]
};
GetPageSizeKey:
PROC [instance: Slate.Instance]
RETURNS [
REF
ANY] = {
x: REF ANY ¬ PropList.GetProp[instance.properties, $PageSize];
IF x=NIL THEN x ¬ $small;
RETURN [x]
};
NewPageHit: XTkWidgets.ButtonHitProcType = {
largeSz: SF.Vec ¬ [768, 1024];
sz: SF.Vec ¬ [0, 0];
d: Data ~ NARROW[registerData];
SELECT GetPageSizeKey[d.instance]
FROM
$small => sz ¬ [0, 0];
$large => sz ¬ largeSz;
ENDCASE => {};
NewPageWithSize[d, sz];
};
SafeHit: XTkWidgets.ButtonHitProcType = {
d: Data ~ NARROW[registerData];
instance: Slate.Instance ~ d.instance;
fileName: Rope.ROPE;
WITH PropList.GetProp[instance.properties, $SafeName]
SELECT
FROM
r: Rope.ROPE => fileName ¬ r;
ENDCASE => fileName ¬ "/tmp/temp.slate";
SlateIO.WriteSlateFile[pages: Slate.CurrentListOfPages[instance.session], fileName: fileName, log: instance.log, server: widget.connection]
};
TrackPageSize:
PROC [d: Data, w: XTk.Widget] = {
XTkWidgets.Choose[w, [callData: GetPageSizeKey[d.instance]]]
};
globalLastSession: Session ¬ NIL;
MakeOrGetSession:
PROC [get:
BOOL ¬
FALSE, bpp:
INT ¬ 1, name: Rope.
ROPE]
RETURNS [session: Session ¬
NIL] = {
stream: IO.STREAM ¬ debugStream;
IF get THEN session ¬ globalLastSession;
IF session=
NIL
THEN {
page: Page;
IF name#
NIL
THEN {
parameter: REF REF ANY ¬ NEW[REF ANY ¬ name];
Slate.CallGlobalEvent[event: $FindSession, callData: parameter];
WITH parameter
SELECT
FROM
s: Session => {globalLastSession ¬ s; RETURN [s]};
ENDCASE => {};
};
session ¬ NewSession[];
page ¬ NewPage[session: session, bpp: bpp];
ActivatePage[page];
IF stream#
NIL
THEN {
FanoutStream.AddSlave[session.debugLog, stream];
};
IF name#NIL THEN Slate.SetSessionName[session, name];
globalLastSession ¬ session;
};
};
debugStream: IO.STREAM ¬ NIL;
SlateCommand: Commander.CommandProc = {
instance: Instance;
session: Session;
d: Data;
n: INT ← 0;
bpp: INT ← 1;
color: BOOL ← FALSE;
createInstance: BOOL ← TRUE;
display: Rope.ROPE ← NIL;
sessionName: Rope.ROPE ← NIL;
DO
arg: Rope.ROPE ← CommanderOps.NextArgument[cmd];
MatchPrefix:
PROC [s:
LONG
STRING]
RETURNS [
BOOL] ~ {
run: INT ~ Rope.Run[s1: arg, s2: ConvertUnsafe.ToRope[s], case: FALSE];
RETURN [run = Rope.Size[arg] AND run > 1];
};
BadSwitch:
PROC = {
CommanderOps.Failed[Rope.Cat["Unknown or ambiguous argument: " , arg, helpMessage]]
};
IF arg=NIL THEN EXIT;
SELECT
TRUE
FROM
MatchPrefix["-color"] => { color ← TRUE; bpp ← 8 };
MatchPrefix["-noInstance"] => { createInstance ← FALSE };
MatchPrefix["-debug"] => { debugStream ← cmd.out };
MatchPrefix["-session"] => { sessionName ← CommanderOps.NextArgument[cmd] };
MatchPrefix["-display"] => { display ← CommanderOps.NextArgument[cmd] };
Rope.Match["-*", arg] => { BadSwitch[] };
ENDCASE => BadSwitch[];
n ← n+1;
ENDLOOP;
session ¬ MakeOrGetSession[bpp: bpp, name: sessionName];
IF createInstance
THEN {
d ← CreateSlateWidget[];
IF color
THEN {
XTk.PutWidgetProp[d.shellWidget, $Color, $Color];
};
XTkWidgets.BindScreenShell[d.shellWidget, display ! Xl.connectionNotCreated =>
CommanderOps.Failed[Rope.Concat["Failed: " , why.reason]]
];
instance ¬ NewInstance[session];
Bind[d, instance];
XTkWidgets.RealizeShell[d.shellWidget, [pos: [2, 2]]];
};
};
InitPages:
PROC [d: Data] = {
EachPage: PageProc = {
IF page#NIL THEN CreatePageIndicator[d, page];
};
[] ¬ EnumeratePages[d.instance.session, EachPage];
LocalIndicate[d, d.instance.session.activePage];
};
FindPageWidget:
PROC [page: Page, d: Data]
RETURNS [pageIndicator: Widget ¬
NIL] = {
No back pointing properties on page: so instances are completely garbage collectable
Each: XTk.EachChild = {
IF XTk.GetWidgetProp[child, keyForPage]=page
THEN {
pageIndicator ¬ child;
stop ¬ TRUE
};
};
IF page#
NIL
THEN
XTk.ShallowInternalEnumerateChildren[d.pagesCont, Each, page];
};
CreatePageIndicator:
PROC [d: Data, page: Page] = {
IF page#
NIL
THEN {
pw: Widget;
numberText: Rope.ROPE;
WITH PropList.GetProp[page.properties, $PageNumber]
SELECT
FROM
ri: REF INT => numberText ¬ IO.PutFR1["%2g", IO.int[ri]];
ENDCASE => numberText ¬ "xx";
pw ¬ XTkWidgets.CreateButton[text: numberText,
hitProc: ActivatePageButtonHit, registerData: d, callData: page
];
XTk.PutWidgetProp[pw, keyForPage, page];
XTkWidgets.AppendChild[d.pagesCont, pw];
};
};
DestroyPageIndicator:
PROC [instance: Instance, page: Page] = {
WITH instance.uiPrivate
SELECT
FROM
d: Data => {
pageIndicator: Widget ¬ FindPageWidget[page, d];
IF pageIndicator#NIL THEN XTk.DestroyWidget[pageIndicator]
};
ENDCASE => {};
};
NewPageHasBeenCreated: EventProc = {
--Called once per instance when new page is created
IF instance#
NIL
THEN
WITH instance.uiPrivate
SELECT
FROM
d: Data => {CreatePageIndicator[d, NARROW[callData]]};
ENDCASE => {};
};
Page numbering is not a global public feature because the file format does not keep track of permanent page numbers
pageNumberKey: REF INT ¬ NEW[INT];
InitPageNumber: PropList.InitializeProcType = {
RETURN [NEW[INT¬0]]
};
NextPageNum:
PROC [session: Session]
RETURNS [i:
INT ¬ 0] = {
x: REF ¬ PropList.GetPropOrInit[session.properties, pageNumberKey, InitPageNumber, session].val;
rNum: REF INT ~ NARROW[x];
Monitored:
PROC [rNum:
REF
INT]
RETURNS [
INT] = {
rNum ¬ rNum+1; RETURN [rNum];
};
IF rNum#NIL THEN i ¬ Monitored[rNum];
};
NewPageHasBeenCreated0: Slate.EventProc = {
--Called once per session when new page is created
page: Page ~ NARROW[callData];
IF page#
NIL
THEN {
num: INT ¬ NextPageNum[page.session];
[] ¬ PropList.PutProp[page.properties, $PageNumber, NEW[INT ¬ num]];
};
};
PageHasBeenRemoved: Slate.EventProc = {
page: Page ~ NARROW[callData];
IF page#
NIL
THEN
WITH instance.uiPrivate
SELECT
FROM
d: Data => {
IF ~d.followMode.follow
AND LocalActivePage[d]=page
THEN {
new: Page ¬ ActivePage[d.instance.session];
IF new=page THEN new ¬ NIL;
LocalActivatePage[d, new];
};
DestroyIndicator[d, page];
};
ENDCASE => {};
};
DestroyIndicator:
PROC [d: Data, page: Page] = {
pageIndicator: Widget ¬ FindPageWidget[page, d];
IF pageIndicator#NIL THEN XTk.DestroyWidget[pageIndicator];
};
LocalIndicate:
PROC [d: Data, page: Page] = {
oldIndicator: Widget ¬ d.usedPageIndicator;
newIndicator: Widget ¬ FindPageWidget[page, d];
IF oldIndicator#NIL AND oldIndicator#newIndicator THEN XTkLabels.SetStyleKey[oldIndicator, NIL];
d.usedPageIndicator ¬ newIndicator;
IF newIndicator#NIL THEN XTkLabels.SetStyleKey[newIndicator, $Gray2]
};
LocalActivePage:
PROC [d: Data]
RETURNS [page: Page] = {
RETURN [d.page];
};
LocalActivatePage:
PROC [d: Data, page: Page] = {
IF d.page#page
THEN {
d.page ¬ page;
d.hasStart ¬ FALSE;
d.mode ¬ NIL;
IF page=
NIL
THEN ResetBM[d, nowhereBM]
ELSE ResetBM[d, page.activeArea];
LocalIndicate[d, page];
};
};
PageHasBeenActivated: Slate.EventProc = {
IF instance#
NIL
THEN
WITH instance.uiPrivate
SELECT
FROM
d: Data =>
IF d.followMode.follow
OR d.page=
NIL
THEN {
LocalActivatePage[d, ActivePage[d.instance.session]];
};
ENDCASE => {};
};
ReceiveMessage: Slate.EventProc = {
IF instance#
NIL
THEN {
message: Rope.ROPE ¬ NARROW[callData];
IO.PutRope[instance.log, message];
};
};
InstName: TYPE = RECORD [instance: Slate.Instance, name: Widget];
CreateSharingWidget: Slate.EventProc = {
shell: Widget ¬ XTkWidgets.CreateShell[
className: $SlateControl,
windowHeader: "Slate Multi-User Ops",
packageName: "SlateControl",
shortName: "Slate Multi"
];
sname: Widget ¬ XTkWidgets.CreateLabel[];
tField: Widget ¬ XTkWidgets.CreateLabeledField[label: "name:", init: "???"];
in: REF InstName ~ NEW[InstName ¬ [instance, tField]];
tq: Xl.TQ ~ GetTQ[instance];
createFollowMenu: XTkPopUps.WidgetCreateClosure ¬ XTkPopUps.WidgetCreateClosureFromChoiceList[list: followChoiceList, defaultNotify: SetFollowButtonHit, notifyTQ: tq];
createSharedToolClosure: XTkPopUps.WidgetCreateClosure ¬ NEW[XTkPopUps.WidgetCreateClosureRec ¬ [CreateSharedToolMenu, in]] ;
sharedTool: Widget ~ XTkPopUps.CreatePopUpButton[
text: "SharedSlate", createMenu: createSharedToolClosure
];
participants: Widget ~ XTkWidgets.CreateButton[
text: "Participants", hitProc: ParticipantsButtonHit, registerData: instance
];
msg: Widget ~ XTkWidgets.CreateButton[
text: "Msg", hitProc: MessageHit, registerData: instance, tq: tq, callData: tField
];
windowFollow: Widget ← XTkPopUps.CreatePopUpButton[
text: "WindowFollowing", createMenu: createFollowMenu, registerData: instance
];
selectSession: Widget ~ XTkWidgets.CreateButton[
text: "SelectSession", hitProc: SelectSessionButtonHit, registerData: instance
];
nameSession: Widget ~ XTkWidgets.CreateButton[
text: "RenameSession", hitProc: RenameSessionHit, registerData: instance, callData: tField
];
xCont: Widget ~ XTkWidgets.CreateXStack[[],
LIST[sharedTool, participants, msg, selectSession, nameSession]
];
xCont2: Widget ~ XTkWidgets.CreateXStack[[],
LIST[windowFollow, sname]
];
topYCont: Widget ← XTkWidgets.CreateYStack[[], LIST[tField, xCont, xCont2]];
XTkSharedCell.BindText[
NARROW[instance.uiPrivate, Data].followTextCell,
windowFollow
];
XTkSharedCell.BindText[instance.session.nameCell, sname];
XTkWidgets.SetShellChild[shell, topYCont];
XTkWidgets.BindScreenShell[shell, callData ! Xl.connectionNotCreated => {
IO.PutRope[instance.log, Rope.Concat["Failed: ", why.reason]];
GOTO oops
}];
SlateControls.LimitLifetime[shell, instance];
XTkWidgets.RealizeShell[shell];
EXITS oops => {}
};
helpMessage: Rope.
ROPE ~ "
Usage: {argument}*
argument:
-color
-debug
-noInstance
-display x-server-name
-session session-name
";
Slate.RegisterGlobalEventProc[$Message, ReceiveMessage, NIL];
Slate.RegisterGlobalEventProc[$AddPage, NewPageHasBeenCreated, NIL];
Slate.RegisterGlobalEventProc[$AddPage0, NewPageHasBeenCreated0, NIL];
Slate.RegisterGlobalEventProc[$RemovePage, PageHasBeenRemoved, NIL];
Slate.RegisterGlobalEventProc[$ActivatePage, PageHasBeenActivated, NIL];
InitFonts[];
InitColors[];
InitFollows[];
SlateControls.RegisterButton[$Main, "Sharing", $CreateSharing];
Slate.RegisterGlobalEventProc[$CreateSharing, CreateSharingWidget];
Commander.Register["Slate", SlateCommand, Rope.Concat["Shared drawing tool", helpMessage]];
END.