MODULE ObxWordEdit;
(**

   project   = "BlackBox"
   organization   = "www.oberon.ch"
   contributors   = "Oberon microsystems"
   version   = "System/Rsrc/About"
   copyright   = "System/Rsrc/About"
   license   = "Docu/BB-License"
   purpose   = "Some examples of how the Word Automation Interface 9.0 can be used."
   changes   = ""
   issues   = ""

**)

   IMPORT CtlWord9, CtlT, StdLog, Fonts, Services;

   VAR

      app: CtlWord9.Application;
      connectDoc: CtlWord9.Document;
   PROCEDURE GetApplication (): CtlWord9.Application;

   BEGIN
      RETURN app
   END GetApplication;
   (* Connecting / Disconnecting *)

   PROCEDURE Connect*;

   (* Connects to a Word application. If Word is not running, it is started. *)
   BEGIN
      connectDoc := CtlWord9.NewDocument();
      connectDoc.Windows().Item(CtlT.Int(1)).PUTVisible(FALSE);
      app := connectDoc.Application();
      StdLog.String(' Connected to Word.'); StdLog.Ln()
   END Connect;
   PROCEDURE Disconnect*;

   (* Disconnects from Word. If nobody else is using the application, it is terminated. *)
   BEGIN
      connectDoc.Close(NIL, NIL, NIL);
      app := NIL;
      StdLog.String(' Disconnected to Word.'); StdLog.Ln()
   END Disconnect;
   (* Starting / Quitting *)

   PROCEDURE Start*;

   (* Starts a new Word application. *)
   BEGIN
      app := CtlWord9.NewApplication();
      app.Options().PUTSmartCutPaste(FALSE);
      app.PUTVisible(TRUE);
      StdLog.String(' Word started.'); StdLog.Ln()
   END Start;
   PROCEDURE Quit*;

   (* Quits Word. *)
   BEGIN
      app.Quit(CtlT.Bool(FALSE),NIL,NIL); app := NIL;
      StdLog.String(' Word quited.'); StdLog.Ln()
   END Quit;
   PROCEDURE Restart*;

   (* Restarts Word. *)
   BEGIN
      Quit; Start
   END Restart;
   (* File *)

   PROCEDURE NewDoc*;

   (* Creates a new document using CtlWord9.NewDocument() and makes it visible. *)
   (* The document is created in the oldest running Word. If there is no Word running, it is started. *)
      VAR doc: CtlWord9.Document;
   BEGIN
      doc := CtlWord9.NewDocument();
      doc.Windows().Item(CtlT.Int(1)).PUTVisible(TRUE);
      StdLog.String("New Document created. "); StdLog.Ln
   END NewDoc;
   PROCEDURE CreateDoc*;

   (* Creates a new, visible document using CtlWord9.Application.Documents().Add. *)
   (* It is only visible, if the application is visible. *)
      VAR doc: CtlWord9.Document;
   BEGIN
      doc := GetApplication().Documents().Add(NIL, NIL, NIL, CtlT.Bool(TRUE));
      StdLog.String(' Document '); StdLog.String(doc.FullName());
      StdLog.String(' created.'); StdLog.Ln
   END CreateDoc;
   PROCEDURE CreateInvisibleDoc*;

   (* Creates a new, invisible document. It is also invisible, if the application is visible. *)
      VAR doc: CtlWord9.Document;
   BEGIN
      doc := GetApplication().Documents().Add(NIL, NIL, NIL, CtlT.Bool(FALSE));
      StdLog.String(' Document '); StdLog.String(doc.FullName());
      StdLog.String(' created invisible.'); StdLog.Ln
   END CreateInvisibleDoc;
   PROCEDURE OpenDoc (filename: ARRAY OF CHAR);

   (* Opens the file filename. *)
      VAR doc: CtlWord9.Document;
   BEGIN
      doc := GetApplication().Documents().Open(CtlT.Str(filename),NIL,NIL,NIL,NIL,NIL,NIL,NIL,NIL,NIL,NIL,NIL);
      StdLog.String(' Document '); StdLog.String(doc.FullName());
      StdLog.String(' opened.'); StdLog.Ln
   END OpenDoc;
   PROCEDURE OpenDocTest*;

   (* Opens a test file. *)
   BEGIN
      OpenDoc('D:\Users\Juerg\test.doc')
   END OpenDocTest;
   PROCEDURE CloseDoc*;

   (* Closes the active document without saving. *)
      VAR doc: CtlWord9.Document; name: CtlT.Strg;
   BEGIN
      doc := GetApplication().ActiveDocument();
      name := doc.FullName();
      doc.Close(CtlT.Int(CtlWord9.wdDoNotSaveChanges),NIL,NIL);
      StdLog.String(' Document '); StdLog.String(name);
      StdLog.String(' closed.'); StdLog.Ln
   END CloseDoc;
   PROCEDURE SaveAndCloseDoc*;

   (* Saves the active document and closes it. *)
      VAR doc: CtlWord9.Document; name: CtlT.Strg;
   BEGIN
      doc := GetApplication().ActiveDocument();
      name := doc.FullName();
      doc.Close(CtlT.Int(CtlWord9.wdSaveChanges),NIL,NIL);
      StdLog.String(' Document '); StdLog.String(name);
      StdLog.String(' saved and closed.'); StdLog.Ln
   END SaveAndCloseDoc;
   PROCEDURE SaveDoc*;

   (* Saves the active document. *)
      VAR doc: CtlWord9.Document;
   BEGIN
      doc := GetApplication().ActiveDocument();
      doc.Save;
      StdLog.String(' Document '); StdLog.String(doc.FullName());
      StdLog.String(' saved.'); StdLog.Ln
   END SaveDoc;
   PROCEDURE Print*;

   (* Prints the active document. *)
      VAR doc: CtlWord9.Document;
   BEGIN
      doc := GetApplication().ActiveDocument();
      doc.PrintOut(NIL,NIL,NIL,NIL,NIL,NIL,NIL,NIL,NIL,NIL,NIL,NIL,NIL,NIL,NIL,NIL,NIL,NIL);
      StdLog.String(' Document '); StdLog.String(doc.FullName());
      StdLog.String(' printed.'); StdLog.Ln
   END Print;
   (* Application *)

   PROCEDURE DispDocs*;

   (* Displays the full names of all the open documents. *)
      VAR count, i: INTEGER; docs: CtlWord9.Documents;
   BEGIN
      docs := GetApplication().Documents();
      count := docs.Count();
      StdLog.Int(count); StdLog.String(' Documents available.'); StdLog.Ln;
      FOR i := 1 TO count DO
         StdLog.String(docs.Item(CtlT.Int(i)).FullName()); StdLog.Ln
      END
   END DispDocs;
   PROCEDURE DispFontNames*;

   (* Displays the names of the available fonts in Word. *)
      VAR fontNames : CtlWord9.FontNames; count, i: INTEGER;
   BEGIN
      fontNames := GetApplication().FontNames();
      count := fontNames.Count();
      StdLog.Int(count);
      StdLog.String(' FontNames available.');
      StdLog.Ln;
      FOR i := 1 TO count DO
         StdLog.String(fontNames.Item(i)); StdLog.Ln
      END
   END DispFontNames;
   PROCEDURE DispBBFontNames*;

   (* Displays the names of the available fonts in BlackBox. There are more than in Word. *)
      VAR t: Fonts.TypefaceInfo;
   BEGIN
      StdLog.String(' BB FontNames:');
      t := Fonts.dir.TypefaceList();
      WHILE t # NIL DO
         StdLog.String(t.typeface); StdLog.Ln;
         t := t.next
      END
   END DispBBFontNames;
   PROCEDURE DispLanguages*;

   (* Displays the languages available in Word. *)
      VAR count: INTEGER; languages: CtlWord9.Languages; lang: CtlWord9.Language; enum: CtlT.Enumerator;
   BEGIN
      languages := GetApplication().Languages();
      count := languages.Count();
      StdLog.Int(count); StdLog.String(' Languages available.'); StdLog.Ln;
      enum:= languages._NewEnum();
      lang := CtlWord9.ThisLanguage(enum.First());
      WHILE lang # NIL DO
         StdLog.String(lang.NameLocal());
         StdLog.Ln;
         lang := CtlWord9.ThisLanguage(enum.Next())
      END
   END DispLanguages;
   PROCEDURE DispLanguagesAndDictionaries*;

   (* Displays the languages available in Word and whether they have a dictionary. *)
   (* Attention: ActiveSpellingDictionary traps if there is no dictionary available... *)
      VAR count: INTEGER; languages : CtlWord9.Languages; lang: CtlWord9.Language; enum:CtlT.Enumerator;
   BEGIN
      languages := GetApplication().Languages();
      count := languages.Count();
      StdLog.Int(count); StdLog.String(' Languages available.'); StdLog.Ln;
      enum:= languages._NewEnum();
      lang := CtlWord9.ThisLanguage(enum.First());
      WHILE lang # NIL DO
         StdLog.String(lang.NameLocal());
         IF lang.ActiveSpellingDictionary() # NIL THEN
            StdLog.String(' (Dict)')
         END;
         StdLog.Ln;
         lang := CtlWord9.ThisLanguage(enum.Next())
      END
   END DispLanguagesAndDictionaries;
   PROCEDURE UseSmartCutPaste*;

   (* Sets the option SmartCutPaste. *)
   BEGIN
      GetApplication().Options().PUTSmartCutPaste(TRUE);
      StdLog.String(' SmartCutPaste turned on.'); StdLog.Ln
   END UseSmartCutPaste;
   (* Visibility *)

   PROCEDURE MakeWordVisible*;

   (* Makes all documents visible, also the ones that were created invisible. *)
   BEGIN
      GetApplication().PUTVisible(TRUE);
      StdLog.String(' Word made visible.'); StdLog.Ln
   END MakeWordVisible;
   PROCEDURE MakeWordInvisible*;

   (* Makes all documents invisible. *)
   BEGIN
      GetApplication().PUTVisible(FALSE);
      StdLog.String(' Word made invisible.'); StdLog.Ln
   END MakeWordInvisible;
   PROCEDURE MakeWinVisible*;

   (* Makes the first window of the active document visible. *)
   BEGIN
      GetApplication().ActiveDocument().Windows().Item(CtlT.Int(1)).PUTVisible(TRUE);
      StdLog.String(' Window made visible.'); StdLog.Ln
   END MakeWinVisible;
   PROCEDURE MakeWinInvisible*;

   (* Makes the first window of the active document invisible. *)
   BEGIN
      GetApplication().ActiveDocument().Windows().Item(CtlT.Int(1)).PUTVisible(FALSE);
      StdLog.String(' Window made invisible.'); StdLog.Ln
   END MakeWinInvisible;
   PROCEDURE IsWinVisible*;

   (* Displays whether the first window of the active document is visible. *)
   BEGIN
      StdLog.Bool(GetApplication().ActiveDocument().Windows().Item(CtlT.Int(1)).Visible()); StdLog.Ln
   END IsWinVisible;
   (* Document *)

   PROCEDURE Undo*;

   (* Undoes the last action. Actions, such a typing characters, can be merged to one action by Word. *)
      VAR doc: CtlWord9.Document;
   BEGIN
      doc := GetApplication().ActiveDocument();
      IF doc.Undo(CtlT.Int(1)) THEN
         StdLog.String(' Undone.')
      ELSE
         StdLog.String(' Undo not possible.')
      END;
      StdLog.Ln
   END Undo;
   PROCEDURE Redo*;

   (* Redoes the last undone action. *)
      VAR doc: CtlWord9.Document;
   BEGIN
      doc := GetApplication().ActiveDocument();
      IF doc.Redo(CtlT.Int(1)) THEN
         StdLog.String(' Redone.')
      ELSE
         StdLog.String(' Redo not possible.')
      END;
      StdLog.Ln
   END Redo;
   PROCEDURE Protect*;

   (* Protects the active document. *)
   BEGIN
      GetApplication().ActiveDocument().Protect(CtlWord9.wdAllowOnlyFormFields, NIL, NIL);
      StdLog.String('Document locked.'); StdLog.Ln
   END Protect;
   PROCEDURE Unprotect*;

   (* Unprotects the active document. *)
   BEGIN
      GetApplication().ActiveDocument().Unprotect(NIL);
      StdLog.String('Document unlocked.'); StdLog.Ln
   END Unprotect;
   (* Accessing the Content of a Document *)

   PROCEDURE DispContent*;

   (* Displays the content of the active document. *)
      VAR doc: CtlWord9.Document;
   BEGIN
      doc := GetApplication().ActiveDocument();
      StdLog.String('Document content: ');
      StdLog.String(doc.Content().Text()); StdLog.Ln
   END DispContent;
   PROCEDURE DispParagraphs*;

   (* Displays the paragraphs of the active document. *)
      VAR count, i: INTEGER; doc: CtlWord9.Document; paras: CtlWord9.Paragraphs;
   BEGIN
      doc := GetApplication().ActiveDocument();
      paras := doc.Paragraphs();
      count := paras.Count();
      StdLog.Int(count); StdLog.String(' Paragraphs available.'); StdLog.Ln;
      FOR i := 1 TO count DO
         StdLog.String(paras.Item(i).Range().Text());
         StdLog.Ln; StdLog.Ln
      END
   END DispParagraphs;
   PROCEDURE DispListParagraphs*;

   (* Displays the ListParagraphs of the active document. *)
      VAR count, i: INTEGER; doc: CtlWord9.Document; paras: CtlWord9.ListParagraphs;
   BEGIN
      doc := GetApplication().ActiveDocument();
      paras := doc.ListParagraphs();
      count := paras.Count();
      StdLog.Int(count); StdLog.String(' ListParagraphs available.'); StdLog.Ln;
      FOR i := 1 TO count DO
         StdLog.String(paras.Item(i).Range().Text());
         StdLog.Ln; StdLog.Ln
      END
   END DispListParagraphs;
   PROCEDURE DispLists*;

   (* Displays the Lists of the active document. *)
      VAR count, i: INTEGER; doc: CtlWord9.Document; lists: CtlWord9.Lists;
   BEGIN
      doc := GetApplication().ActiveDocument();
      lists := doc.Lists();
      count := lists.Count();
      StdLog.Int(count); StdLog.String(' Lists available.'); StdLog.Ln;
      FOR i := 1 TO count DO
         StdLog.String(lists.Item(i).Range().Text());
         StdLog.Ln; StdLog.Ln
      END
   END DispLists;
   PROCEDURE DispWords*;

   (* Displays the Words of the active document, using the CtlWord9.Document.Words method. *)
      VAR count, i: INTEGER; doc: CtlWord9.Document; words: CtlWord9.Words;
   BEGIN
      doc := GetApplication().ActiveDocument();
      words := doc.Words();
      count := words.Count();
      StdLog.Int(count); StdLog.String(' Words available.'); StdLog.Ln;
      FOR i := 1 TO count DO
         StdLog.String(words.Item(i).Text()); StdLog.Ln
      END
   END DispWords;
   PROCEDURE DispWords2*;

   (* Displays the Words of the active document, using the CtlWord9.Range.Next method. *)
      VAR len: INTEGER; doc: CtlWord9.Document; r: CtlWord9.Range;
   BEGIN
      doc := GetApplication().ActiveDocument();
      len := doc.Characters().Count();
      r := doc.Range(CtlT.Int(0),CtlT.Int(0));
      StdLog.String(' Words: '); StdLog.Ln;
      REPEAT
         r := r.Next(CtlT.Int(CtlWord9.wdWord), NIL);
         StdLog.String(r.Text()); StdLog.Ln
      UNTIL r.End() >= len
   END DispWords2;
   PROCEDURE DispWords3*;

   (* Displays the Words of the active document, using the CtlWord9.Range.MoveEnd method. *)
      VAR moved, lastEnd: INTEGER; doc: CtlWord9.Document; r: CtlWord9.Range;
   BEGIN
      doc := GetApplication().ActiveDocument();
      r := doc.Range(CtlT.Int(0),CtlT.Int(0));
      lastEnd := -1;
      StdLog.String(' Words: '); StdLog.Ln;
      REPEAT
         lastEnd := r.End();
         moved := r.MoveEnd(CtlT.Int(CtlWord9.wdWord), CtlT.Int(1));
         StdLog.String(r.Text()); StdLog.Ln;
         r.Collapse(CtlT.Int(CtlWord9.wdCollapseEnd))
      UNTIL lastEnd = r.End()
   END DispWords3;
   PROCEDURE DispCharacters*;

   (* Displays the Characters of the active document. *)
      VAR count, i: INTEGER; doc: CtlWord9.Document; chars: CtlWord9.Characters;
   BEGIN
      doc := GetApplication().ActiveDocument();
      chars := doc.Characters();
      count := chars.Count();
      StdLog.Int(count); StdLog.String(' Characters available.'); StdLog.Ln;
      FOR i := 1 TO count DO
         StdLog.String(chars.Item(i).Text()); StdLog.String(' ')
      END
   END DispCharacters;
   PROCEDURE DispSentences*;

   (* Displays the Sentences of the active document. *)
      VAR count, i: INTEGER; doc: CtlWord9.Document; sentences: CtlWord9.Sentences;
   BEGIN
      doc := GetApplication().ActiveDocument();
      sentences := doc.Sentences();
      count := sentences.Count();
      StdLog.Int(count); StdLog.String(' Sentences available.'); StdLog.Ln;
      FOR i := 1 TO count DO
         StdLog.String(sentences.Item(i).Text());
         StdLog.Ln; StdLog.Ln
      END
   END DispSentences;
   PROCEDURE DispStoryRanges*;

   (* Displays the StoryRanges of the active document. *)
      VAR count, i: INTEGER; doc: CtlWord9.Document; storyRanges: CtlWord9.StoryRanges;
   BEGIN
      doc := GetApplication().ActiveDocument();
      storyRanges := doc.StoryRanges();
      count := storyRanges.Count();
      StdLog.Int(count); StdLog.String(' StoryRanges available.'); StdLog.Ln;
      FOR i := 1 TO count DO
         StdLog.String(storyRanges.Item(i).Text());
         StdLog.Ln; StdLog.Ln
      END
   END DispStoryRanges;
   PROCEDURE DispRuns*;

   (* Should write the runs of the active document, but it does not work! How can we get the runs? *)
      VAR len: INTEGER; doc: CtlWord9.Document; r: CtlWord9.Range;
   BEGIN
      doc := GetApplication().ActiveDocument();
      len := doc.Characters().Count();
      r := doc.Range(CtlT.Int(0),CtlT.Int(0));
      StdLog.String('Runs: '); StdLog.Ln;
      REPEAT
         r := r.Next(CtlT.Int(CtlWord9.wdCharacterFormatting), NIL);
         StdLog.String(r.Text()); StdLog.Ln
      UNTIL r.End() >= len
   END DispRuns;
   (* Editing Text *)

   PROCEDURE AppendThisText*;

   (* Appends "This Text" to the end of the active document. This means that it is insert in front of the last 0DX. *)
      VAR doc: CtlWord9.Document;
   BEGIN
      doc := GetApplication().ActiveDocument();
      doc.Content().InsertAfter('This Text');
      StdLog.String('This Text appended.'); StdLog.Ln
   END AppendThisText;
   PROCEDURE OverwriteLastCharWithA*;

   (* Overwrites the last character, which is always a 0DX, with "A". Word then inserts a new 0DX at the end. *)
      VAR count: INTEGER; doc: CtlWord9.Document;
   BEGIN
      doc := GetApplication().ActiveDocument();
      count := doc.Characters().Count();
      doc.Range(CtlT.Int(count-1), CtlT.Int(count)).PUTText('A');
      StdLog.String('Last Character overwritten.'); StdLog.Ln
   END OverwriteLastCharWithA;
   PROCEDURE CopyText*;

   (* Copies and inserts the first 10 chars at the beginning of the active document. *)
      VAR doc: CtlWord9.Document; text: CtlT.Strg;
   BEGIN
      doc := GetApplication().ActiveDocument();
      text := doc.Range(CtlT.Int(0),CtlT.Int(10)).Text();
      doc.Range(CtlT.Int(0),CtlT.Int(0)).PUTText(text);
      StdLog.String('Text copied.'); StdLog.Ln
   END CopyText;
   PROCEDURE CopyFormattedText*;

   (* Copies and inserts the first 10 chars at the beginning of the active document, formatted. *)
      VAR doc: CtlWord9.Document; formattedText: CtlWord9.Range;
   BEGIN
      doc := GetApplication().ActiveDocument();
      formattedText := doc.Range(CtlT.Int(0),CtlT.Int(10)).FormattedText();
      doc.Range(CtlT.Int(0),CtlT.Int(0)).PUTFormattedText(formattedText);
      StdLog.String('Formatted text copied.'); StdLog.Ln
   END CopyFormattedText;
   PROCEDURE DeleteText*;

   (* Deletes the first 10 character of the active document. *)
      VAR doc: CtlWord9.Document;
   BEGIN
      doc := GetApplication().ActiveDocument();
      doc.Range(CtlT.Int(0),CtlT.Int(10)).PUTText('');
      StdLog.String('Text deleted.'); StdLog.Ln
   END DeleteText;
   (* Font *)

   PROCEDURE Disp2ndParagraphFontName*;

   (* Displays the name of the font of the 2nd paragraph of the active document, if it is defined. *)
      VAR doc: CtlWord9.Document; fontName: ARRAY 100 OF CHAR;
   BEGIN
      doc := GetApplication().ActiveDocument();
      fontName := doc.Paragraphs().Item(2).Range().Font().Name()$;
      IF fontName[0] = 0X THEN
         StdLog.String('2nd Paragraph Font Name is not defined (result = empty string)')
      ELSE
         StdLog.String('2nd Paragraph Font Name = '); StdLog.String(fontName)
      END;
      StdLog.Ln
   END Disp2ndParagraphFontName;
   PROCEDURE Is1stParagraphBold*;

   (* Displays whether the 1st paragraph of the active document is bold or not, if it is defined. *)
      VAR bold: INTEGER; doc: CtlWord9.Document;
   BEGIN
      doc := GetApplication().ActiveDocument();
      bold := doc.Paragraphs().Item(1).Range().Bold();
      StdLog.String('The first paragraph ');
      IF bold = 0 THEN
         StdLog.String('is Not Bold')
      ELSIF bold = -1 THEN
         StdLog.String('is Bold')
      ELSE
         StdLog.String('is neither Bold nor Not Bold')
      END;
      StdLog.String(' (result = '); StdLog.Int(bold); StdLog.String(')'); StdLog.Ln
   END Is1stParagraphBold;
   PROCEDURE Disp1stParagraphFontSize*;

   (* Displays the size of the font of the 1st paragraph of the active document, if it is defined. *)
      VAR size: SHORTREAL; doc: CtlWord9.Document;
   BEGIN
      doc := GetApplication().ActiveDocument();
      StdLog.String('The first paragraph: size =');
      size := doc.Paragraphs().Item(1).Range().Font().Size();
      IF size # CtlWord9.wdUndefined THEN
         StdLog.Real(size)
      ELSE
         StdLog.String('undefined (result = '); StdLog.Real(size); StdLog.String(' )')
      END;
      StdLog.Ln
   END Disp1stParagraphFontSize;
   (* Performance *)

   PROCEDURE Performance*;

      CONST
         wordTries = 1000;
         bbTries = 100000000;
      VAR start, counterTime, wordTime, bbTime : LONGINT; i : INTEGER; opt: CtlWord9.Options;
      PROCEDURE Put (newValue: BOOLEAN);

         VAR value: BOOLEAN;
      BEGIN
         value := newValue
      END Put;
   BEGIN

      IF app = NIL THEN Start END;
      opt := GetApplication().Options();
      start:= Services.Ticks();

      FOR i := 1 TO wordTries DO END;
      counterTime := Services.Ticks() - start;
      start:= Services.Ticks();

      FOR i := 1 TO wordTries DO opt.PUTSmartCutPaste(TRUE) END;
      wordTime := Services.Ticks() - start - counterTime;
      StdLog.Int(wordTries);
      StdLog.String(" PUTSmartCutPaste took ");
      StdLog.Real(wordTime / Services.resolution);
      StdLog.String(" sec."); StdLog.Ln;
      start:= Services.Ticks();

      FOR i := 1 TO bbTries DO END;
      counterTime := Services.Ticks() - start;
      start := Services.Ticks();

      FOR i := 1 TO bbTries DO Put(TRUE) END;
      bbTime := Services.Ticks() - start - counterTime;
      StdLog.Int(bbTries);
      StdLog.String(" BlackBox procedure calls took ");
      StdLog.Real(bbTime / Services.resolution);
      StdLog.String(" sec."); StdLog.Ln;
      StdLog.String("Properties are ");

      StdLog.Int( (wordTime * bbTries) DIV (bbTime * wordTries));
      StdLog.String(" times slower."); StdLog.Ln
   END Performance;
END ObxWordEdit.