=
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 [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];
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]
RETURNS[v: ViewerClasses.Viewer] = {
v ← Labels.Create[info: [
parent: container,
name: label,
wx: x,
wy: y,
wh: rowH,
border: FALSE]];
v ← ViewerTools.MakeNewTextViewer[info: [
parent: container,
wx: x + v.ww,
wy: y,
ww: columnWidth - v.ww - 40,
wh: rowH,
border: FALSE]];
ViewerTools.InhibitUserEdits[v];
};
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:
LIST
OF
ROPE, 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: "PermutePhrases", proc: PermutePhrasesButton, line: 1];
InsertMenuEntry[name: "SetPosition", proc: SetPositionButton, line: 1];
ViewerOps.SetMenu[container, menu];
indexHandle.indexPhrasesContainer ← PhraseButtons["Index Entry Phrases:", firstColumn, thisY];
indexHandle.sortAsPhrasesContainer ← PhraseButtons["Sort As:", secondColumn, thisY];
NextRow[4];
indexHandle.rangeChoice ← Choices[CONS["Single", CONS["BeginRange", CONS["EndRange", NIL]]], firstColumn, thisY];
indexHandle.primaryChoice ← Choices[CONS["Ordinary", CONS["Primary", NIL]], secondColumn, thisY];
NextRow[];
indexHandle.bytePositionBox ← FeedbackLabel["Byte position:", secondColumn, thisY];
indexHandle.seePhrasesContainer ← PhraseButtons["See phrases:", firstColumn, thisY];
NextRow[4];
Rule[1];
indexHandle.entriesBox ← FeedbackLabel["Entries:", firstColumn, thisY];
indexHandle.rangesBox ← FeedbackLabel["Ranges:", secondColumn, thisY];
NextRow[];
indexHandle.seeCountBox ← FeedbackLabel["See references:", firstColumn, thisY];
indexHandle.nestingCountBox ← FeedbackLabel["Deepest Nesting:", secondColumn, thisY];
NextRow[];
Rule[1];
indexHandle.indexViewer ← ViewerOps.CreateViewer[
flavor: $Index,
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 = {
indexHandle: IndexHandle ← NARROW[clientData, IndexHandle];
selectedViewer: ViewerClasses.Viewer ← ViewerTools.GetSelectedViewer[];
documentName:
ROPE ←
IF selectedViewer = NIL OR selectedViewer.class.get = NIL THEN NIL
ELSE NARROW[selectedViewer.class.get[selectedViewer, $SelChars]];
IF documentName.Length <= 1
THEN {
IF (selectedViewer =
NIL)
THEN {
Sorry["No selection in a text viewer"];
documentName ← NIL;
}
ELSE 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];
indexEntry: IndexEntry ← NEW[IndexEntryRec];
phrases: LIST OF ROPE ← NIL;
choice: ROPE;
ixRope: ROPE;
IF indexHandle.bytePosition = 0
THEN {
Sorry["No position established for this index entry yet"];
RETURN;
};
indexEntry.phrases ← CollectPhrases[indexHandle.indexPhrasesContainer];
indexEntry.bytePosition ← indexHandle.bytePosition;
indexEntry.stickyPointer ← TextNode.LocWithin[
TiogaOps.ViewerDoc[indexHandle.documentViewer],
indexHandle.bytePosition];
indexEntry.valid ← TRUE;
indexEntry.versionStamp ← IndexToolPrivate.IndexVersionStamp[TiogaOps.GetRope[indexEntry.stickyPointer.node]];
indexEntry.primary ← Rope.Equal["Primary", ChoiceButtons.GetSelectedButton[indexHandle.primaryChoice]];
choice ← ChoiceButtons.GetSelectedButton[indexHandle.rangeChoice];
indexEntry.type ←
SELECT
TRUE
FROM
Rope.Equal["Single", choice] => single,
Rope.Equal["BeginRange", choice] => beginRange,
Rope.Equal["EndRange", choice] => endRange,
ENDCASE => ERROR;
indexEntry.seePhrases ← CollectPhrases[indexHandle.seePhrasesContainer];
IF indexEntry.seePhrases # NIL THEN indexEntry.type ← see;
indexEntry.sortAsPhrases ← CollectPhrases[indexHandle.sortAsPhrasesContainer];
IndexTree.InsertNewIndexEntry[indexHandle, indexEntry ! IndexTree.DuplicateKey => GOTO Duplicate];
ixRope ← IndexProperties.IndexEntryToRope[indexEntry];
ixRope ← Rope.Concat[NARROW[TiogaOps.GetProp[indexEntry.stickyPointer.node, $Index], ROPE], ixRope];
TiogaOps.PutProp[indexEntry.stickyPointer.node, $Index, ixRope];
ViewerOps.SetNewVersion[indexHandle.documentViewer];
set feedback selection and normalize indexViewer
NormalizeIndexViewer[indexHandle, indexEntry];
EXITS
Duplicate => Sorry["Index entry duplicates one already in table; NOT inserted."];
};
NormalizeIndexViewer:
PROCEDURE [indexHandle: IndexHandle, ix: IndexEntry] = {
SetFeedback[indexHandle, ix];
ViewerOps.PaintViewer[indexHandle.indexViewer, client];
};
SetFeedback:
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];
};
CollectPhrases:
PROCEDURE [phraseContainer: ViewerClasses.Viewer]
RETURNS [phrases:
LIST
OF
ROPE ←
NIL] = {
v: ViewerClasses.Viewer ← phraseContainer.child;
phrase: ROPE;
WHILE v #
NIL
DO
IF v.scrollable
THEN {
phrase ← ViewerTools.GetContents[v];
IF phrase = NIL OR phrase.Length = 0 OR phrase.Equal[" "] THEN EXIT;
TRUSTED {phrases ← LOOPHOLE[List.Nconc1[LOOPHOLE[phrases], phrase]]};
};
v ← v.sibling;
ENDLOOP;
};
DeleteEntryButton: Menus.ClickProc = {
indexHandle: IndexHandle ← NARROW[clientData, IndexHandle];
};
PermutePhrasesButton: Menus.ClickProc = {
indexHandle: IndexHandle ← NARROW[clientData, IndexHandle];
};
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[];
};
SetPositionButton: Menus.ClickProc = {
indexHandle: IndexHandle ← NARROW[clientData, IndexHandle];
selectedViewer: ViewerClasses.Viewer ← ViewerTools.GetSelectedViewer[];
should check, but we don't yet, that selectedViewer is the documentViewer!
IF selectedViewer #
NIL
THEN {
indexHandle.bytePosition ← ViewerTools.GetSelection[selectedViewer].start;
ViewerTools.SetContents[indexHandle.bytePositionBox, IO.PutFR["%g", IO.card[indexHandle.bytePosition]]];
};
};
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;
};
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[];
phrase:
ROPE ←
IF selectedViewer = NIL OR selectedViewer.class.get = NIL THEN NIL
ELSE NARROW[selectedViewer.class.get[selectedViewer, $SelChars]];
IF phrase.Length < 1
THEN {
Sorry["No selection in a text viewer"];
phrase ← NIL;
}
ELSE {
IF (indexHandle.bytePosition = 0
OR phraseData.phraseNumber = 1)
AND selectedViewer #
NIL
THEN {
indexHandle.bytePosition ← ViewerTools.GetSelection[selectedViewer].start;
ViewerTools.SetContents[indexHandle.bytePositionBox, IO.PutFR["%g", IO.card[indexHandle.bytePosition]]];
};
ViewerTools.SetContents[phraseData.viewer, phrase];
ViewerTools.SetSelection[phraseData.viewer, atTheBeginning];
};
};
blue => {
ViewerTools.SetContents[phraseData.viewer, NIL];
ViewerTools.SetSelection[phraseData.viewer, atTheBeginning];
};
ENDCASE;
};
ExecCommand: Commander.CommandProc = {
documentName: ROPE ← NIL;
documentName ←
IO.GetTokenRope[
IO.
RIS[cmd.commandLine],
IO.IDProc !
IO.EndOfStream => CONTINUE].token;
[] ← IndexToolPrivate.CreateIndexFromDocument[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: ExecCommand,
doc: "Create an index tool."
];