Take the 2-minute tour ×
Mathematica Stack Exchange is a question and answer site for users of Mathematica. It's 100% free, no registration required.

What are the best (most robust and most convenient) ways to create palettes that can be installed permanently (using Palettes -> Install Palette...) and are safe to use? I'd be interested in how other people have done this in the past to learn more about idiomatic front end programming.

I put some code illustrating the pattern I am using now at the end of this post. I'd appreciate some comments on it.

Notes and requirements:

  1. The palette should always work, regardless of whether the kernel has been quit or an evaluation is running.

  2. The palette may have more than one button which share some code between them.

  3. It must not pollute the Global` context or change the kernel state in a way that might break something unexpectedly.

  4. I'm looking for an easy way to define palettes (minimal boilerplate code and extra work)

  5. It would be nice (non-essential) if several versions of the palette could coexist independently (my current approach doesn't have this because it uses its own context to hide its function definition, but everything in this context is shared)

  6. It would also be nice (non-essential) to integrate documentation in an easy way (help button bringing it up maybe?)


My current approach is illustrated below. It "localizes" its symbols by putting them in a separate context, and uses DynamicModule to ensure that all the definitions are done before any button code is run.

SetAttributes[paletteButton, HoldAll]
paletteButton[name_, tooltip_, func_, opt : OptionsPattern[]] := 
 Tooltip[Button[name, Unevaluated[func], Appearance -> "Palette", 
   opt], tooltip, TooltipDelay -> Automatic]

Begin["SomePalette`"];

PaletteNotebook[
 DynamicModule[{},
  Column[{
    paletteButton["One", "Button one", function[1]],
    paletteButton["Two", "Button two", function[2]]
    }],

  Initialization :> (
    function[x_] := MessageDialog[x]
    )
  ],
 WindowTitle -> "Some Palette"
 ]

End[];
share|improve this question
1  
BTW I always found it odd that you there isn't an 'uninstall palette' menu item. –  Sjoerd C. de Vries Jan 18 '12 at 15:54
2  
@Sjoerd It's more difficult to implement, but I agree, it should be present. Palettes usually go into FileNameJoin[{$UserBaseDirectory, "SystemFiles", "FrontEnd", "Palettes"}] –  Szabolcs Jan 18 '12 at 16:02
    
Note: this is a question asking about "best-practices" / "what is idiomatic" and what different ways are there to achieve this. I already have a solution which satisfies most requirements I have, but it would be tremendously helpful to at least hear from others who have made palettes in the past. I am not sure if these types of questions will be welcome on the site or not. –  Szabolcs Jan 18 '12 at 18:02
    
I saw the title of this question and thought: "This sounds like a good question for Szabolcs!" I think I learned about non-trivial palettes from your web site. :-) –  Mr.Wizard Jan 22 '12 at 7:01
    
@Mr.Wizard Unfortunately not. I only made that simple little TablePaste palette because I was tired of importing data from website table manually. But as you can see the code is even included three times in that one (so ugly!) I didn't know anything about making palettes properly, I'm just learning now –  Szabolcs Jan 22 '12 at 8:47

2 Answers 2

up vote 19 down vote accepted
+100

All palette state (i.e., variables which affect the palette and should be remembered between sessions) should be vectored through the palette's TaggingRules option, and its initialization should be done in the palette's NotebookDynamicExpression option. That, plus context isolation of any kernel functions you need to define should solve all of the points you raise, excepting the documentation issue.

An example palette which demonstrates these principles:

CreatePalette[
 Column[{Button["Print opener state", 
    MyPalette`Private`DoSomething[
     "The opener is " <> 
      If[CurrentValue[EvaluationNotebook[], {TaggingRules, "opener"}],
        "open", "closed"]]],
   OpenerView[{"Group of buttons", Column[{Button[1], Button[2]}]}, 
    Dynamic[CurrentValue[
      EvaluationNotebook[], {TaggingRules, "opener"}, False]]]}],
 NotebookDynamicExpression :> 
  Refresh[MyPalette`Private`DoSomething[MyPalette`Private`x_] := 
    Print[MyPalette`Private`x], None]]

Mathematica graphics

Let's hit the items raised in this code one by one...

  • The palette uses a kernel-defined function which is in NotebookDynamicExpression. The code is wrapped in Refresh[_,None] to ensure that it evaluates once only when the notebook is opened. The code is context isolated by hand. Note that Begin and End won't work here, although they would work inside of a package, or if you wrapped the code in ToExpression (e.g., Begin["foo`"];ToExpression["code"];End[]).
  • A palette-wide state variable is stored in the palette's TaggingRules, which can be accessed by using CurrentValue[EvaluationNotebook[],{TaggingRules,"opener"}]. Because "opener" is a string, no symbols are introduced into any context.
  • State variables will typically need to be initialized. I could do that in various standard ways, but I used the undocumented third argument to CurrentValue which sets it to False if it doesn't already have a value.
  • Once the palette is installed, the TaggingRules setting will persist between instances of the palette, even if you quit Mathematica. Mathematica automatically serializes an installed palette's TaggingRules settings when you close it by storing the value into the global option PalettesMenuSettings.
  • If you have multiple versions of the palette open, they'll each operate using independent state variables because the state variable is attached to the palette notebook. If multiple versions of the palette are installed under different names then the PalettesMenuSettings trick will store the TaggingRules separately.
share|improve this answer
1  
Again, the highlight is TaggingRules. I have one concern about using NotebookDynamicExpression with Refresh[..., None] instead of the Initialization option of a DynamicModule wrapping everything: the definitions won't survive a kernel restart if we use Refresh[..., None]. Is there any reason not to use a DynamicModule[..., Initialization :> ...] here? Regarding Begin and End: I think if we evaluate Begin["..."] before evaluating the CreatePalette[...], then evaluate End[] after, everything new symbol in the palette will be correctly localized into the proper... –  Szabolcs Feb 1 '12 at 16:33
    
... context. Is this correct? (We just need to take care that Begin, CreatePalette and End are sent to the kernel as separate expressions to ensure that Begin will affect the parsing of CreatePalette). –  Szabolcs Feb 1 '12 at 16:34
    
@Szabolcs The Initialization option of DynamicModule is fine if all of the code you're initializing for is inside that DynamicModule. What you cannot do is to make assumptions about the order in which subsequent DynamicModule or Dynamic expressions will fire, or even if they will fire (if they're not in the same position). If NotebookDynamicExpression doesn't fire in new kernel sessions, it certainly should. I admit to not having tried this recently. –  John Fultz Feb 6 '12 at 15:12
    
@Szabolcs What I meant is that you can't use Begin and End inside of Initialization without forcing a separate parse stage to begin, as would happen with Get or ToExpression. Yes, you should be able to use Begin/End outside of CreatePalette, but only if they are on separate lines of input. I.e., they can't be a part of a CompoundExpression or other expression which contains the CreatePalette command. This means terminating all operators and placing a newline in the input cell, or putting the Begin/End in totally different cells. –  John Fultz Feb 6 '12 at 15:19
    
Is it okay to store large data in TaggingRules such as 5-10 thumbnail images (of the size returned by Thumbnail), or is this likely to cause performance problems because the system was not designed for this? –  Szabolcs Apr 21 '12 at 18:38

You could generate the palette from code in a separate notebook, and have the generated palette use a unique context by setting CellContext -> Notebook when creating the palette notebook.

I think this should help with items 2, 3, and 5.


Example (there may be better ways..)

CreateDocument[
   {Cell[BoxData[MakeBoxes[x = 2]], "Input"]}, 
   CellContext -> Notebook]

If you then look at Context[x] in the created notebook, you get something like Notebook$$21$666892`

share|improve this answer
2  
I think you should mention that this can be done with setting CellContext->Notebook for the PaletteNotebook. –  Albert Retey Jan 21 '12 at 21:21
    
@AlbertRetey Agreed, it's good to be detailed about that. –  Brett Champion Jan 21 '12 at 21:29
    
Could you give a code example? If I simply add CellContex -> Notebook to PaletteNotebook in my example above, function will still be created in the SomePalette` context (or Global` if I remove Begin/End). This is because contexts are decided at parse time. –  Szabolcs Jan 27 '12 at 11:25

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.