IndexViewerImpl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Created by Rick Beach, July 12, 1983 4:34 pm
Rick Beach, March 31, 1985 2:11:35 am PST
DIRECTORY
Basics,
Buttons,
ChoiceButtons,
Commander USING [CommandProc, Register],
Containers,
IconRegistry,
Icons,
IndexNotify,
IndexProperties,
IndexToolPrivate,
IndexTree,
IndexViewer,
IO,
Labels,
List,
Menus,
MessageWindow,
NumberLabels USING [CreateNumber, NumberLabelUpdate],
OrderedSymbolTableRef,
Rope,
Rules,
TEditDocument,
TEditScrolling,
TextNode,
TiogaOps,
TiogaOpsDefs,
TIPUser,
ViewerClasses,
ViewerOps,
ViewerSpecs,
ViewerTools;
IndexViewerImpl: CEDAR PROGRAM
IMPORTS Buttons, ChoiceButtons, Commander, Containers, IconRegistry, Icons, IndexNotify, IndexProperties, IndexToolPrivate, IndexTree, IO, Labels, List, Menus, MessageWindow, NumberLabels, OrderedSymbolTableRef, Rope, Rules, TEditScrolling, TextNode, TiogaOps, TIPUser, ViewerOps, ViewerSpecs, ViewerTools
EXPORTS IndexViewer, TiogaOpsDefs
= BEGIN OPEN IndexToolPrivate;
ROPE: TYPE = Rope.ROPE;
Comparison: TYPE = Basics.Comparison;
NodeBody: PUBLIC TYPE = TextNode.Body;
PhraseData: TYPE = REF PhraseDataRec;
PhraseDataRec: TYPE = RECORD [
indexHandle: IndexHandle,
phraseNumber: CARDINAL ← 0,
viewer: ViewerClasses.Viewer ← NIL];
NewTool: PUBLIC PROCEDURE [kindOfIndex: ATOM, documentName: ROPE]
RETURNS [indexHandle: IndexHandle ← NEW[IndexHandleRec]] = {
firstColumn: INTEGER = 0;
secondColumn: INTEGER = ViewerSpecs.openRightWidth/2;
columnWidth: INTEGER = ViewerSpecs.openRightWidth/2;
numberOfPhrases: INTEGER = 10;
phraseSize: INTEGER = secondColumn - 100;
defaultPhraseRows: INTEGER = 3;
h: INTEGER = ViewerSpecs.captionHeight;
rowH: INTEGER = ViewerSpecs.messageWindowHeight;
thisY: INTEGER ← 0;
NextRow: PROCEDURE [rows: INTEGER ← 1] = {
thisY ← thisY + rows*rowH;
};
menu: Menus.Menu ← Menus.CreateMenu[2];
container: ViewerClasses.Viewer ←
Containers.Create[
info: [
name: IF documentName.Length = 0 THEN "IndexTool" ELSE Rope.Concat["Index for ", documentName],
column: right,
iconic: TRUE,
scrollable: FALSE],
paint: TRUE];
InsertMenuEntry: PROCEDURE [name: Rope.ROPE, proc: Menus.MenuProc, line: Menus.MenuLine] = {
menuEntry: Menus.MenuEntry ← Menus.CreateEntry[
name: name,
proc: proc,
clientData: indexHandle,
documentation: NIL,
fork: FALSE,
guarded: FALSE];
Menus.InsertMenuEntry[menu, menuEntry, line];
};
PhraseButtons: PROCEDURE [label: ROPE, x, y: INTEGER] RETURNS [phraseContainer: ViewerClasses.Viewer] = {
saveY: INTEGER ← y;
Leave space for the header button above the phrase container
thisY ← y;
NextRow[];
phraseContainer ← Containers.Create[
info: [
scrollable: TRUE,
border: FALSE,
parent: container,
wx: x,
wy: thisY,
ww: columnWidth,
wh: defaultPhraseRows*rowH],
paint: FALSE];
FOR i: INTEGER DECREASING IN [1..numberOfPhrases] DO
Create the phrase viewers in reverse order since they are searched in list order by CollectPhrases
phraseData: PhraseData ← NEW[PhraseDataRec ← [indexHandle, i]];
button: Buttons.Button ← Buttons.Create[
info: [
name: " ",
wx: 0,
wy: (i-1)*rowH,
ww: 0,
wh: h,
parent: phraseContainer,
scrollable: FALSE,
border: TRUE],
clientData: phraseData,
proc: PhraseButton,
paint: FALSE];
phraseData.viewer ← ViewerTools.MakeNewTextViewer[
info: [
wx: button.cw + 6*i,
wy: (i-1)*rowH,
ww: phraseContainer.cw - (button.cw + 6*i) - 5,
wh: rowH,
parent: phraseContainer,
scrollable: TRUE,
border: TRUE],
paint: FALSE];
ViewerOps.AddProp[phraseData.viewer, $IndexPhrase, $TRUE];
ENDLOOP;
[] ← Buttons.Create[
info: [
name: label,
wx: x,
wy: y,
wh: rowH,
parent: container,
scrollable: FALSE,
border: FALSE],
proc: ClearPhrasesButton,
clientData: phraseContainer,
paint: FALSE];
thisY ← y;
};
FeedbackLabel: PROCEDURE [label: ROPE, x, y: INTEGER, charsInNumber: NAT ← 4]
RETURNS[v: NumberLabels.NumberLabel] = {
v ← Labels.Create[
info: [name: label, parent: container, wx: x, wy: y, wh: rowH, border: FALSE]
paint: FALSE
];
v ← NumberLabels.CreateNumber[
info: [
parent: container,
wx: x + v.ww,
wy: y,
ww: columnWidth - v.ww - 40,
wh: rowH,
border: FALSE],
chars: charsInNumber,
initialValue: 0,
paint: FALSE
];
};
Rule: PROCEDURE [h: CARDINAL] = {
rule: Rules.Rule ← Rules.Create[info: [
parent: container,
wx: 0,
wy: thisY,
ww: 0,
wh: h]];
Containers.ChildXBound[container, rule];
};
Choices: PROCEDURE [choices: Phrases, x, y: INTEGER] RETURNS [choiceRef: ChoiceButtons.EnumTypeRef] = {
choiceRef ← ChoiceButtons.BuildEnumTypeSelection[viewer: container, x: x, y: y, buttonNames: choices, clientdata: indexHandle, style: menuSelection];
};
InsertMenuEntry[name: "CopyIndexTo", proc: CopyIndexToButton, line: 0];
InsertMenuEntry[name: "NewIndexTool", proc: NewIndexToolButton, line: 0];
InsertMenuEntry[name: "DeleteEntry", proc: DeleteEntryButton, line: 0];
InsertMenuEntry[name: "InsertEntry!", proc: InsertEntryButton, line: 0];
InsertMenuEntry[name: "AddNewKind", proc: AddNewKindButton, line: 1];
InsertMenuEntry[name: "PermutePhrases", proc: PermutePhrasesButton, line: 1];
ViewerOps.SetMenu[container, menu];
indexHandle.kindOfEntryChoices ← Choices[LIST["Ordinary", "See", $SeeAlso], firstColumn, thisY];
NextRow[];
indexHandle.indexPhrasesContainer ← PhraseButtons["Index Entry Phrases:", firstColumn, thisY];
indexHandle.sortAsPhrasesContainer ← PhraseButtons["Sort As:", secondColumn, thisY];
NextRow[4];
indexHandle.seePhrasesContainer ← PhraseButtons["See phrases:", firstColumn, thisY];
NextRow[4];
Rule[1];
indexHandle.entriesLabel ← FeedbackLabel["# Entries:", firstColumn, thisY];
indexHandle.seeCountLabel ← FeedbackLabel["# See references:", secondColumn, thisY];
NextRow[];
indexHandle.startPositionLabel ← FeedbackLabel["Start position:", firstColumn, thisY];
indexHandle.endPositionLabel ← FeedbackLabel["End position:", secondColumn, thisY];
NextRow[];
Rule[1];
indexHandle.indexViewer ← ViewerOps.CreateViewer[
flavor: $TiogaButtons,
info: [
wx: firstColumn,
wy: thisY,
parent: container,
scrollable: TRUE],
paint: FALSE
];
indexHandle.indexViewer.tipTable ← indexViewerClass.tipTable;
Containers.ChildXBound[container, indexHandle.indexViewer];
Containers.ChildYBound[container, indexHandle.indexViewer];
ViewerOps.AddProp[indexHandle.indexViewer, $IndexHandle, indexHandle];
indexHandle.rootIndexBranch ← NARROW[indexHandle.indexViewer.data, TEditDocument.TEditDocumentData].text;
};
NewIndexToolButton: Menus.ClickProc = {
selectedViewer: ViewerClasses.Viewer ← ViewerTools.GetSelectedViewer[];
documentName: ROPE ← ViewerTools.GetSelectionContents[];
IF selectedViewer = NIL THEN {
Sorry["Make a text selection of the document name (longer than 1 character)"];
documentName ← NIL;
}
ELSE IF documentName.Length <= 1 THEN
documentName ← selectedViewer.name;
};
[] ← IndexToolPrivate.CreateIndexFromDocument[documentName];
};
Sorry: PROCEDURE [rope: ROPE] = {
MessageWindow.Append[message: rope, clearFirst: TRUE];
MessageWindow.Blink[];
};
InsertEntryButton: Menus.ClickProc = {
indexHandle: IndexHandle ← NARROW[clientData, IndexHandle];
selectedViewer: ViewerClasses.Viewer ← ViewerTools.GetSelectedViewer[];
check that the selection is in the expected document indexHandle.documentViewer
IF selectedViewer # NIL THEN {
Sorry["Make a text selection in the document for this index entry];
RETURN;
}
ELSE IF selectedViewer # indexHandle.documentViewer THEN {
Sorry["Can't index that document from this index tool"];
RETURN;
}
ELSE {
phrases: Phrases ← CollectPhrases[indexHandle.indexPhrasesContainer];
IF NumberOfPhrases[phrases] = 0 THEN {
Sorry["Supply an index phrase before inserting it!];
RETURN;
}
ELSE {
ix: IndexEntry ← NEW[IndexEntryRec];
ix.phrases ← phrases;
establish position from current selection span: TiogaOps.GetSelection[primary].start & .end
IF indexHandle.bytePosition = 0 THEN {
Sorry["No position established for this index entry yet"];
RETURN;
};
ix.kindOfEntry ← ChoiceButtons.GetSelectedButton[indexHandle.kindOfEntryChoices];
ix.seePhrases ← CollectPhrases[indexHandle.seePhrasesContainer];
IF ix.seePhrases # NIL AND ix.kindOfEntry # $See AND ix.kindOfEntry # $SeeAlso THEN
ix.kindOfEntry ← $See;
ix.sortAsPhrases ← CollectPhrases[indexHandle.sortAsPhrasesContainer];
IndexTree.InsertNewIndexEntry[indexHandle, ix !
IndexTree.DuplicateKey => GOTO Duplicate];
ixRope ← IndexProperties.IndexEntryToRope[ix];
ixRope ← Rope.Concat[NARROW[TiogaOps.GetProp[ix.stickyPointer.node, $Index], ROPE], ixRope];
TiogaOps.PutProp[ix.stickyPointer.node, $Index, ixRope];
TextEdit.PutCharProp[node~, index~, name~ix.kindOfIndex, value~ix, nChars~, root~RootOf[node]];
ViewerOps.SetNewVersion[indexHandle.documentViewer];
set feedback selection and normalize indexViewer
NormalizeIndexViewer[indexHandle, ix];
UpdateFeedback[indexHandle.entriesLabel, indexHandle.entries ← indexHandle.entries + 1];
IF indexHandle.kindOfEntry = $See THEN
UpdateFeedback[indexHandle.seeCountLabel, indexHandle.seeCount ← indexHandle.seeCount + 1];
UpdateFeedback[indexHandle.nestingCountLabel, indexHandle.nestingCount ← MAX[NumberOfPhrases[indexHandle.phrases], indexHandle.nestingCount]];
};
};
EXITS
Duplicate => Sorry["Index entry duplicates one already in table; NOT inserted."];
};
NormalizeIndexViewer: PROCEDURE [indexHandle: IndexHandle, ix: IndexEntry] = {
nearestEntry: IndexEntry;
item, leftItem, equalItem, rightItem: OrderedSymbolTableRef.Item;
[leftItem, equalItem, rightItem] ← OrderedSymbolTableRef.Lookup3[indexHandle.indexTable, ix];
item ← IF equalItem # NIL THEN equalItem ELSE IF leftItem # NIL THEN leftItem ELSE rightItem;
nearestEntry ← NARROW[item, IndexEntry];
TiogaOps.SelectNodes[viewer: indexHandle.indexViewer,
start: nearestEntry.branchLocationStart.node,
end: nearestEntry.branchLocationEnd.node,
level: word,
which: feedback];
TEditScrolling.AutoScroll[indexHandle.indexViewer, TRUE, FALSE, feedback];
ViewerOps.PaintViewer[indexHandle.indexViewer, client];
};
CollectPhrases: PROCEDURE [phraseContainer: ViewerClasses.Viewer]
RETURNS [phrases: Phrases ← NIL] = {
v: ViewerClasses.Viewer ← phraseContainer.child;
phrase: ViewerTools.TiogaContents;
WHILE v # NIL DO
IF ViewerOps.FetchProp[v, $IndexPhrase] # NIL THEN {
phrase ← ViewerTools.GetTiogaContents[v];
IF phrase = NIL OR phrase.contents.IsEmpty = 0 THEN EXIT;
TRUSTED {phrases ← LOOPHOLE[List.Nconc1[LOOPHOLE[phrases], phrase.contents]]};
TRUSTED {phrases ← LOOPHOLE[List.Nconc1[LOOPHOLE[phrases], phrase.formatting]]};
};
v ← v.sibling;
ENDLOOP;
};
NumberOfPhrases: PROC [phrases: Phrases] RETURNS [NAT] ~ TRUSTED {
RETURN [List.Length[LOOPHOLE[phrases]]/2];
};
DeleteEntryButton: Menus.ClickProc = {
indexHandle: IndexHandle ← NARROW[clientData, IndexHandle];
};
PermutePhrasesButton: Menus.ClickProc = {
indexHandle: IndexHandle ← NARROW[clientData, IndexHandle];
};
AddNewKindButton: Menus.ClickProc = {
indexHandle: IndexHandle ← NARROW[clientData, IndexHandle];
ix.kindOfEntry ← ChoiceButtons.GetSelectedButton[indexHandle.kindOfEntryChoices];
};
CopyIndexToButton: Menus.ClickProc = {
indexHandle: IndexHandle ← NARROW[clientData, IndexHandle];
IF TiogaOps.GetSelection[primary].viewer = NIL THEN {
Sorry["No primary selection. Can't copy index."];
RETURN;
};
TiogaOps.SelectNodes[viewer: indexHandle.indexViewer,
start: TiogaOps.FirstChild[indexHandle.rootIndexBranch],
end: TiogaOps.LastLocWithin[indexHandle.rootIndexBranch].node,
which: secondary];
TiogaOps.ToPrimary[];
};
UpdateFeedback: PROC [numberLabel: NumberLabels.NumberLabels, value: INT] ~ {
IF numberLabel.destroyed THEN RETURN;
NumberLabels.NumberLabelUpdate[numberLabel, value];
};
ClearPhrasesButton: Buttons.ButtonProc = {
phraseContainer: ViewerClasses.Viewer ← NARROW[clientData, ViewerClasses.Viewer];
v: ViewerClasses.Viewer ← phraseContainer.child;
WHILE v # NIL DO
ViewerTools.SetContents[v, NIL];
v ← v.sibling;
ENDLOOP;
ViewerTools.SetSelection[phraseContainer.child, atTheBeginning];
};
PhraseButton: Menus.ClickProc = {
phraseData: PhraseData ← NARROW[clientData, PhraseData];
indexHandle: IndexHandle ← phraseData.indexHandle;
atTheBeginning: ViewerTools.SelPos = NEW[ViewerTools.SelPosRec ← [0, 0, FALSE, before]];
SELECT mouseButton FROM
yellow =>
Sorry["Left-click to copy selection to phrase viewer, Right-click to clear phrase viewer"];
red => {
selectedViewer: ViewerClasses.Viewer ← ViewerTools.GetSelectedViewer[];
IF selectedViewer = NIL THEN
Sorry["Please make a text selection of the phrase to index"];
ELSE {
start, end: TiogaOps.Location;
root: TiogaOps.Ref ← TiogaOps.SelectionRoot[primary]
[start~start, end~end] ← TiogaOps.GetSelection[primary];
check that the phrase is within a single node
IF start.node # end.node THEN
Sorry["Index phrases must be within a single node"];
ELSE {
ViewerTools.SetContents[phraseData.viewer, NIL];
TiogaOps.SelectDocument[phraseData.viewer, char, secondary];
TiogaOps.ToSecondary[]; -- copy the selected phrase
};
};
};
blue => {
ViewerTools.SetContents[phraseData.viewer, NIL];
ViewerTools.SetSelection[phraseData.viewer, atTheBeginning];
};
ENDCASE;
};
IndexToolCommand: Commander.CommandProc = {
documentName: ROPENIL;
documentName ← IO.GetTokenRope[IO.RIS[cmd.commandLine], IO.IDProc !
IO.EndOfStream => CONTINUE].token;
we should accept a document kind and create an atom with that name
[] ← IndexToolPrivate.CreateIndexFromDocument[$Index, documentName];
};
IndexViewerPaint: ViewerClasses.PaintProc = {
IF self.iconic THEN {
Icons.DrawIcon[flavor: indexViewerIcon,
context: context, x: 0, y: 0,
label: self.name];
}
ELSE
textPaintProc[self, context, whatChanged, clear];
};
indexViewerClass: ViewerClasses.ViewerClass =
NEW[ViewerClasses.ViewerClassRec ← ViewerOps.FetchViewerClass[$Text]^];
textPaintProc: ViewerClasses.PaintProc = indexViewerClass.paint;
indexViewerIcon: Icons.IconFlavor;
indexViewerClass.notify ← IndexNotify.IndexNotifier;
indexViewerClass.cursor ← bullseye;
indexViewerClass.paint ← IndexViewerPaint;
indexViewerClass.icon ← private;
indexViewerClass.tipTable ← TIPUser.InstantiateNewTIPTable["IndexTool.tip"];
IconRegistry.RegisterIcon["IndexTool", "IndexTool.icons", 0];
indexViewerIcon ← IconRegistry.GetIcon["IndexTool", tool];
ViewerOps.RegisterViewerClass[$Index, indexViewerClass];
Commander.Register[
key: "IndexTool",
proc: IndexToolCommand,
doc: "Create an index tool."
];
END.