=
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: ROPE ← NIL;
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."
];