Front End Programming

John M. Novak
Wolfram Research, Inc.

This notebook is an outline of the talk given at the 1999 Mathematica Developer's Conference. It includes some useful code snippets and examples, but little explanation. For detailed explanations, see the references, in particular the chapter in Elements of Mathematica Programming, on which this talk was substantially based.

References:
• Elements of Mathematica Programming, Troels Petersen, ed., TELOS/Spring-Verlag, Forthcoming.
http://library.wolfram.com/conferences/conference98/Novak/FEProgrammingTalk.nb
• The Beginner's Guide to Mathematica Version 4, Theodore Gray and Jerry Glynn, Cambridge University Press, 1999.

The focus of this talk is on notebook manipulation; there is another set of issues associated with typesetting and interpretation of expressions.

Important Concepts

The notebook is an expression. Options to the expression control many visible characteristics. ◆

Manipulation of a notebook is best done by acting on a part of the notebook: the current selection. ◆

The kernel and front end are separate programs; it is important to know what happens where when programming in Mathematica. ◆

Currently, the tool for driving an interactive interface is the button, in the form of ButtonBox and associated ButtonFunction. ◆

The Notebook is an Expression

The Cell Expression

Cell[data, style, options]

Cell Data Types

Most frequently seen cell data types (first argument of Cell expression).

TextData, BoxData, GraphicsData, RawData, CellGroupData

Cell Styles

Dictated by style sheet.

Some common styles:

"Title", "Section", "Subsection", "Text", "Input", "Output", "Graphics"

Determine default options to be used for cell.

Examples

The Notebook Expression

Notebook[{cell1,  cell2, …}, options]

Example

Manipulating Notebooks

Handling Generated Notebooks

Separation of FE and kernel creates a need for a 'handle' to a notebook expression that is in the front end.

NotebookObject[ ... ]

A few handles to specific notebooks:

SelectedNotebook[],  InputNotebook[],  EvaluationNotebook[],  
    ButtonNotebook[]

Some commands:

Notebooks[], NotebookGet[nb], NotebookWrite[nb, expr], NotebookRead[nb]

Examples

Put a small silly notebook:

Read it back into the kernel:

Write a new cell on the end of the notebook (as long as the selection wasn't changed):

Changing the Selection

Current selection corresponds to a highlighted section of a notebook in the front end. The current selection can be changed in a number of ways, such as moving it and searching for matching text.

SelectionMove[nb, direction, type, (count)]

NotebookFind[nb, text, (direction), (texttype)]

Examples

Continuing to use the notebook generated in the previous section...

Move current highlighted area in notebook:

Read highlighted area:

Write over current highlight:

Search for cell of specified style:

Changing Options

SetOptions[obj, opts]

Setting for a whole Notebook

Setting for the selection

The notebook handle applies to the whole notebook; how do we pick out the current selection to act on?

NotebookSelection[NotebookObject[…]]

(Below assumes selection hasn't been changed on the notebook created in the previous selection:)

Tagging Cells

It is very useful to be able to assign a 'name' to a cell. The CellTags option lets us do this.

Cell[data,style, CellTags->{"\!\(\*
StyleBox[\"name1\",\nFontSlant->\"Italic\"]\)","\!\(\*
StyleBox[\"name2\",\nFontSlant->\"Italic\"]\)",...}]

NotebookFind[nb, "\!\(\*
StyleBox[\"name\",\nFontSlant->\"Italic\"]\)",Next  |  All, CellTags]

Example

Create a tagged cell:

Find the above-created cell and set an option:

A More Useful Example

A program to create a progress-bar sort of dialog:

CreateProgress[desctext_String:"Progress :"] :=
NotebookPut[Notebook[{
   Cell[desctext, "Text",
            FontWeight -> "Bold",FontSize -> 18],
   Cell["",
            CellTags -> "ProgressBar",
            CellFrame -> True,
            Background -> Hue[0],
            CellFrameMargins -> {{0,0},{Inherited,Inherited}},
            CellMargins -> {{10,190}, {Inherited,Inherited}}
    ]},
	ShowCellBracket -> False,
	WindowSize -> {200, 100},
	WindowMargins -> {{Automatic,Automatic},{Automatic,10}},
	WindowTitle -> "Progress",
	WindowElements -> {}]
]

UpdateProgress[nb_NotebookObject,perc_, wid_:200,lrmargins_:10] :=
	(NotebookFind[nb,"ProgressBar", All, CellTags];
     SetOptions[NotebookSelection[nb],
			CellMargins ->
			  {{lrmargins, wid(1-perc) + lrmargins (2 perc - 1)},
			   {Inherited,Inherited}}
     ])

CloseProgress[nb_NotebookObject] :=
    NotebookClose[nb, Interactive -> False]

The dialog can be opened; UpdateProgress sprinkled throughout your routine, followed by the close before exiting your routine.

progressdialog  =  CreateProgress[]

Do[UpdateProgress[progressdialog, n]; 
      Pause[1], {n, 0, 1, 0.2}]

CloseProgress[progressdialog]

Generalization to the Front End

The front end as a whole also has an object, stored in $FrontEnd. It only has options that can be changed.

SetOptions[$FrontEnd, opts]

Examples

A useful function to redirect error messages to the messages window:

Attributes[MessagesToConsole] = {HoldAll};

MessagesToConsole[expr_] :=
    With[{messageopts = Options[$FrontEnd, MessageOptions]},
        SetOptions[$FrontEnd,
            MessageOptions -> 
              {"KernelMessageAction" -> {"Beep", "PrintToConsole"}}
        ];
        CheckAll[expr,
            (SetOptions[$FrontEnd, ##]& @@ messageopts;
             If[#2 === Hold[], #1, ReleaseHold[#2]] &)
        ]
    ]

Compare the following behaviors (assuming default settings in your front end):

MessagesToConsole[1/0]

1/0

The Front End and the Kernel are Different Programs

The Front End has its Own Language

Kernel functions that operate in the front end are in fact 'calling back' to the front end to execute a command in the front end's language.

Commands evaluated in the front end via menu items, key combinations, buttons, and sent from an external link (kernel).

Many front end operations do not have a 'function name' equivalent; they are triggered by 'Front End Tokens'.

The Kernel can only evaluate one thing at a time

If a Front End command triggers a full kernel evaluation, the trigger is queued up to activate after the current evaluation completes. If the command was triggered by a kernel operation, and the kernel then goes on to remove the basis for the queued evaluation, then no more will happen.

Example

The following sequence of operations works.

The logical conclusion is to put the pieces together:

But it doesn't work.

The revised approach is to use NotebookRead

But better not to do it at all.

The Kernel does not yet have a way to wait for a Front End event

There is no mechanism yet for the kernel to 'wait' on a front end event; code that wants to start something in the front end, then wait for a response before continuing in the kernel needs to use a busy loop checking for an innocuous front end change.

A Dialog Box

TypesetInput[theprompt_:""] :=
Module[{thenb, result},
	thenb = openTheDialog[theprompt];
	While[Options[thenb, Visible] ===
			{Visible -> True},
		Do[Null, {250}]];
	NotebookFind[thenb,
			"TheInput",All, CellTags];
	result = NotebookRead[thenb];
	NotebookClose[thenb];
	MakeExpression[result[[1,1]]]
]

openTheDialog[theprompt_] := 
NotebookPut[Notebook[{
	Cell[Switch[Head[theprompt],
			String, TextData[theprompt],
			BoxData | TextData, theprompt,
			_, BoxData[ToBoxes[ theprompt]]],
		"Text",
		Editable -> False],
	Cell[BoxData[""],"Input",
            CellFrame -> True,
            Evaluatable -> False,
            ShowCellTags -> False,
            CellTags -> "TheInput",
            CellMargins -> {{10,10},{4,4}},
            Deletable -> False],
    Cell[BoxData[
    	ButtonBox["OK",
           ButtonFunction :> 
               SetOptions[ButtonNotebook[],
                    	Visible -> False],
           ButtonFrame -> "DialogBox"]],
        "Input",
            Editable -> False,
            Active -> True,
            TextAlignment -> Center,
            CellMargins -> {{10,10},{200,2}}]},
    ShowCellBracket->False,
    WindowFrame -> "MovableModalDialog",
    WindowFloating -> False,
    WindowSize -> {300, 150},
    WindowFrameElements -> {},
    WindowElements -> {"VerticalScrollBar"}
]];

TypesetInput["Tell me how many:"]

TypesetInput[
      TextData[{"No, tell me ", 
          StyleBox["how",  
            FontSlant -> "Italic"], " many"}]]

TypesetInput[
      HoldForm[(∫_a^b) 
                (x/(((x + 3))^3)) \[DifferentialD]x]]

The Interactive Interface

Buttons

The most important attribute of a button is the button function. This dictates what the button will do.

The button can do what it does directly in the FE, or by calling the kernel; be aware of what is happening where.

Evaluating in the front end is equivalent to the setting
ButtonEvaluator->None.

So, if the button operation can be expressed as a sequential series of front-end operation involving notebooks with symbolic names (ButtonNotebook, InputNotebook, etc.), evaluation can occur entirely in the front end. If any variables need to be used, flow control is required, or more complex computations are needed, evaluation must occur in the kernel, calling back to the FE as appropriate.

The button function is more general; it accepts arguments which can be passed to the function. In the simplest form, the first argument is the contents of the button, unless the button data is set, in which case that is used instead.

Another gotcha; I couldn't say ButtonFunction :> Print above; substitution is done in the FE, which doesn't support full range of kernel interpretation.

A Small Applet

The following code cells must be evaluated before the applet can work. The applet window can be gotten by pressing here.

Storage of applet state: TaggingRules

Drawing a graphic to a notebook: create a cell by using DisplayString to get the contents.

Set up functions to perform the actions. It is nice to be able to use one function as a dispatch function for multiple buttons, since for maintenance it is easier to change code than buttons.

Lots of possible improvements. One key issue involves saving the code. Can use a package, tagging rules, etc. Also, error checking, message redirection, handling of syntax errors, automatic determination of variables, changing other image attributes, and so on.

Polyhedron Explorer ◆

Polyhedron Explorer is another example of a small applet; this one with code embedded in the button functions (though I would not consider this the ideal solution; if I were rewriting it, I would put the code in a tagging rule, and modify the button functions to check whether the code has been loaded, and extract it if not).