RTML in menus: foreground text colour

In a group of databases containing structured data, from which it is possible to analyse the tables to present it in a hierarchical manner, I find it useful to present that hierarchy as a menu tree — just as I almost always find a hierarchical menu (such as Classic Menu or Folder Glance) the quickest way to locate a file on my various hard drives. Therefore creating a menu tree for the data set I’m working on was a higher priority for me than a search form. And, thanks to LMSL, it was much easier than I expected: very straightforward to build each submenu as a text array, using the appropriate primary key as the name of each of the many nested submenus. The only temporary stumbling block was realising (which should have been obvious) that not only do all submenus have to be defined before the main menu, but all sub-submenus must be defined before the submenu to which they are attached, and so on up the tree. Therefore it’s important to assemble all submenus in the right order in the variable to be passed to ‘filemenubar’ (or whichever command).

In this particular dataset it’s important to be able to use italics for individual words within each menu item, therefore the ‘<i’ option in the menu item suffix, which renders the whole line in italic, isn’t appropriate. Therefore I decided to use RTML which again worked perfectly (because the relevant fields in the tables were already marked-up with RTML tags anyway) once I realised that I needed ‘@#RichText;’ in the suffix of each submenu title, not just that of the root menu.

However, this reveals a problem with the foreground colour of menu text. A normal Panorama X menu follows the usual MacOS colour-scheme. For MacOS 10.12, that is:

Normal mode: Black text (0x000000) on pale grey background (0xf6f6f6)
Dark mode*: White text (0xffffff) on dark-ish grey background (0x373737)
Highlighted menu item (in both cases): white text on mid-grey background (0x6f6f72)

* (System Preferences > General > Use dark menu bar and Dock)

However, when RTML is enabled using ‘@#RichText;’, the default foreground colour is always black, regardless of whether or not dark mode is selected, or whether the item is highlighted or not — and black text doesn’t show up well against a background colour of 0x373737.

Since I usually use dark mode, one way round this would be to add ‘color:ffffff’ to every item in every menu and submenu. However, that would be cumbersome, would make the menu variable significantly longer and the menu perhaps slower to display, and, most important, it would make the menus completely illegible if dark mode were then disabled (0xffffff on 0xf6f6f6 is not a good look — less than 4% difference between the colours!)

I can see the logic for enforcing a fixed default foreground colour when RTML provides the tag to enable colour to be changed selectively, just as HTML does. However, non-RTML menus also allow the foreground colour to be changed (of the whole menu item) yet observe the OS′s default black or white if it isn’t. Whether enabling RTML in a menu or not, it would be foolish to choose any foreground colour except one that would stand out against any likely background (e.g. bright red, 0xff0000). Speaking for myself, there are several markup options which I might consider applying to menu items apart from italic — bold, justification, etc.— but colour would not be one of them.

How easy would it be to tweak things to use the OS default foreground colour (white for highlighted items and black or white, as appropriate, for the rest) in RTML menus too?

Sorry: the penultimate paragraph of my last message should begin ‘I can see the logic for enforcing a fixed default foreground colour when RTML provides the <color> tag to enable colour to be changed selectively . . .’

What is LMSL?

Live Menu Specification Language. It has a help page.

If you declare the color on the main menu title then all the following menu items will default to that color unless individually changed in the remaining menu items. Here is an example I put together to set all the menu items to red if my system is not in dark mode and white if it is in dark mode.

let theMode=tagdata(applescript({tell application "System Events"
	tell appearance preferences
		get properties
	end tell
end tell}),"dark mode:",",",1)
If theMode="true"
    letfileglobal txtColor="FFFFFF"
else
    letfileglobal txtColor="FF0000"
endif
letfileglobal theMenus=
menu("Offices","color",txtColor)+
    menuitem("California")+
    menuitem("Los Angeles","INDENT",1)+
    menuitem("Downtown","INDENT",2)+
    menuitem("LAX","INDENT",2)+
    menuitem("San Francisco","INDENT",1)+
    menuitem("Sacramento","INDENT",1)+
    menuitem("Arizona")+
    menuitem("Phoenix","INDENT",1,"Color","0000FF")+
    menuitem("Tuscon","INDENT",1)+
    menuitem("Flagstaff","INDENT",1)

I have only tested the applescript on my old macOS 10.13.6 system so I don’t know for sure it still works on later systems. I simply put theMenus variable as a formula for a Popup Menu Button set to Formula as my test and it worked as expected. I was able to over-ride the color for the Phoenix menu item to blue and it did not affect anything else. If you change the mode after running the procedure it will have to be run again to update.

I know the best solution is if Panorama X automatically set the default to the current mode but I’m hoping this is of use in your situation for the time being. For Jim’s future information, if he ever has time to investigate this, there is a discussion on stackoverflow.com:

If you declare the color on the main menu title then all the following menu items will default to that color unless individually changed in the remaining menu items.

Yes, but not when using rich text, which was my point. If you add the pseudo-font declaration which enables RTML, other style information you might have declared in the menu header is ignored. Thus your example, but with rich text enabled and the word ‘Francisco’ in italic:

let theMode=tagdata(applescript({tell application “System Events”

tell appearance preferences

  get properties

end tell

end tell}),“dark mode:”,“,”,1)

If theMode=“true”

letfileglobal txtColor=“0000FF”

else

letfileglobal txtColor=“FF0000”

endif

letfileglobal theMenus=

menu(“Offices”,“color”,txtColor,“font”,“#RichText”)+

menuitem(“California”)+

menuitem("Los Angeles","INDENT",1)+

menuitem(“Downtown”,“INDENT”,2)+

menuitem(“LAX”,“INDENT”,2)+

menuitem("San <i>Francisco</i>","INDENT",1)+
menuitem("Sacramento","INDENT",1)+

menuitem(“Arizona”)+

menuitem("Phoenix","INDENT",1,"Color","0000FF")+

menuitem(“Tuscon”,“INDENT”,1)+

menuitem("Flagstaff","INDENT",1)

Or, a little more succinctly using LMSL (and much easier to create using an existing array):

let theMode=tagdata(applescript({tell application “System Events” to tell appearance preferences to get properties}),“dark mode:”,”,",1)

If theMode=“true”

letfileglobal txtColor=“FFFFFF”

else

letfileglobal txtColor=“FF0000”

endif

letfileglobal theMenus=

“(Offices) #”+txtColor+"@#RichText;

California

Los Angeles +1

Downtown +2

LAX +2

San Francisco +1

Sacramento +1

Arizona

Phoenix +1#0000FF

Tuscon +1

Flagstaff +1

"

In both cases, colour definitions are ignored*, both for the whole menu and an individual item, and the foreground colour remains black whether in dark mode or not. As I said, the one thing I don’t want to change in any menu or menu item is its colour, especially if it has to be item-by-item rather than per-menu. Without rich text, the default foreground colour of menus is automatically in contrast to the background, whether dark or not, but with rich text enabled it’s always black.

  • as one might expect, because RTML has its own method of colour definition, the tag — but, in a menu, that only applies to an individual line, e.g.

    color:0000FFPhoenix +1

Your method is using Applescript to establish whether dark mode is enabled is a good idea, and could be used to apply a, intelligently chosen colour definition to every entry in every menu, but that would fall down if the display mode were changed before the next updating of the menu.

Applying RTML to menus was clearly an afterthought — see Jim’s post in September 2016:

Hmm, I just realized it might be possible to get rich text to work in menu items and buttons, I’ll have to put that into the “that would be cool someday” pile

and it appears to have been rapidly implemented in v.0.1.026 the same month. A very good afterthought!

Hmmm. Applying to posts by e-mail doesn’t work as well as the forum suggests it should. Trying that again, with apologies:

On 21 Apr 2020, at 19:35, Gary Yonaites via Panorama Discussion Forum <webmaster@provue.com> wrote:

If you declare the color on the main menu title then all the following menu items will default to that color unless individually changed in the remaining menu items.

Yes, but not when using rich text, which was my point. If you add the pseudo-font declaration which enables RTML, other style information you might have declared in the menu header is ignored. Thus your example, but with rich text enabled and the word ‘Francisco’ in italic:

let theMode=tagdata(applescript({tell application "System Events"
	tell appearance preferences
		get properties
	end tell
end tell}),"dark mode:",",",1)
If theMode="true"
    letfileglobal txtColor="FFFFFF"
else
    letfileglobal txtColor="FF0000"
endif
letfileglobal theMenus=
menu("Offices","color",txtColor,"font","#RichText")+
    menuitem("California")+
    menuitem("Los Angeles","INDENT",1)+
    menuitem("Downtown","INDENT",2)+
    menuitem("LAX","INDENT",2)+
    menuitem("San <i>Francisco</i>","INDENT",1)+
    menuitem("Sacramento","INDENT",1)+
    menuitem("Arizona")+
    menuitem("Phoenix","INDENT",1,"Color","0000FF")+
    menuitem("Tuscon","INDENT",1)+
    menuitem("Flagstaff","INDENT",1)

Or, a little more succinctly using LMSL (and much easier to create using an existing array):

let theMode=tagdata(applescript({tell application "System Events" to tell appearance preferencesto get properties}),"dark mode:",",",1)
If theMode="true"
	letfileglobal txtColor="FFFFFF"
else
	letfileglobal txtColor="FF0000"
endif
letfileglobal theMenus=
"(Offices)	#"+txtColor+"@#RichText;
	California
	Los Angeles	+1
	Downtown	+2
	LAX	+2
	San <i>Francisco</i>	+1
	Sacramento	+1
	Arizona
	Phoenix	+1#0000FF
	Tuscon	+1
	Flagstaff	+1
"

In both cases, colour definitions are ignored *, both for the whole menu and an individual item, and the foreground colour remains black whether in dark mode or not. As I said, the one thing I would rather not change in any menu or menu item is its colour, especially if it has to be item-by-item rather than per-menu. Without rich text, the default foreground colour of menus is automatically in contrast to the background, whether dark or not, but with rich text enabled it’s always black.

* as one might expect, because RTML has its own method of colour definition, the tag — but, in a menu, that only applies to an individual line, e.g.

<color:0000FF>Phoenix +1

Your method is using Applescript to establish whether dark mode is enabled is a good idea, and could be used to apply an intelligently chosen colour definition to every entry in every menu, but that would fall down if the display mode were changed before the next updating of the menu.

Applying RTML to menus was clearly an afterthought — see Jim’s post in September 2016:

Hmm, I just realized it might be possible to get rich text to work in menu items and buttons, I’ll have to put that into the “that would be cool someday” pile

and it appears to have been rapidly implemented in v.0.1.026 the same month. A very good afterthought!

No, I think that would work. Panorama recalculates the menu each time you click on it.

True, but the entire feature is based on AppKit’s NSAttributedString class. It basically makes that class accessible to Panorama users. When I wrote that post in September 2016, I had just discovered that buttons and menu items could use NSAttributedString. Since I had already gotten NSAttributedString to work in Text Display objects, it turned out to be fairly simple to do the same for menus and buttons.

I’m bringing this up to point out that I didn’t make up this feature, but rather exposed it. So the way it works is determined by Apple engineers, I have only limited ability to customize it. So the menu color issue works that way because that’s the way Apple designed it to.

What I did contribute was the idea of using tags (RTML) to specify the styled text. Apple’s NSAttributedString uses a byzantine interface that even advanced programmer’s find very difficult to work with. My RTML language makes it pretty easy, especially if you have any HTML background. At some point I may open source the RTML component so that other programmer’s could use it as well.

If you eliminate the semicolon after @#RichText the colors will display properly but the italicized Francisco will display as <i>Francisco</i>. I can’t seem to get that to work unless I change the code to italicize the entire line with “San Francisco +1<i”. That is about everything I could think of to try and probably not of much help.

Yes. That’s because rich text is enabled by changing font to the pseudo-font #RichText. When using LMSL, a font change is achieved by sandwiching the name between @ and ; in the suffix of the font name . Without the semicolon the font change fails, rich text is not enabled and the RTML tags <i>and </i> (amongst others) are displayed instead of being acted upon.

Not in the way that I meant, I think. I was referring to reading the dark-mode status at the time the menu is compiled, and hardcoding the <color:000000> or <color:ffffff> tags into every line of every menu and submenu. The only way to change that global menu colour would be either to recompile the menu string or to do a replace() operation on it to change all the tags, before repeating the filemenubar statement to update the menu. (In this case, compiling the menu tree is a slow process, involving multiple lookups between several different tables for thousands of records, whereas the menu string thus created can easily subsequently be modified incrementally as records are added, deleted or modified to the databases. And it appears that the filemenubar statement, to initialise or re-initialise a menu, works very quickly even with a complex menu tree (although I’m in the early stages of testing that side of things, as I explore what Panorama X is capable of).

That’s what I feared, but thank you for such a clear response. Just to be clear, does that mean that each menu item is a separate NSAttributedString, and that inevitably means that any style changes could only ever apply within the bounds of an individual menu item, as at present? it would make a huge difference if it were possible to add style changes to a whole RTML (sub)menu, as it already is (by different means) if RTML is not enabled, Something along the lines of:

(Menu name)	*S@#RichText;<color:FF0000><b>
	This text is bold and red,
	As is this (regardless of dark mode),
	But <color:0000FF>this</color> is bold and blue

I imagine that would probably mean Panorama X silently repeating at the beginning of every item in the menu the NSAtrributedString attributes specified by the RTML in the header, but that would at least be more efficient than decoding the same RTML tag at the beginning of every item.

In my own case I’m happy to hardwire white text into all my menus if I have to, because until this issue arose I hadn’t turned dark mode off since it was first introduced and I don’t want to. However, it would be more elegant and efficient to make such a change per-menu rather than per-item if it were possible. Others might find it useful to apply, say, a font change to a whole menu, yet also benefit from RTML’s flexibility of style markup within each item, e.g.:

(Menu name)	*S@#RichText;<font:Courier:14>
	14pt Courier
	14pt Courier <i>italic</i>, <b<bold</b> . . .
	. . . and <i><b>bold italic</b></i>

It certainly does!

Yes, that’s what it means.

I could possibly come up with a hack to repeat a section of text at the start of each menu, but there’s no reason you couldn’t do that yourself. You don’t have to write your LMSL as a single text constant, you could create a variable with the color style you want and bring that variable into each line.

Ok, I looked at the LMSL documentation and it doesn’t mention that it is a formula, not constant text. It’s complicated enough as it is. What you are really creating is not text, but a formula to generate text in LMSL format. Each time the menu is clicked, the formula is evaluated. You may be thinking the formula is evaluated when you run filemenubar, but that is not the case, the formula is stored, and doesn’t actually run until later. So IF there was a function that could find out the system dark mode status, you could do this. But there isn’t. Though if there is some command line program that can do this, you could do it with the shellscript( function. Normally I wouldn’t suggest that, but you sound like the sort of user that might be able to pull that off. Hmm, maybe you could, this page indicates that there is a shell command to find the status:

defaults read -g AppleInterfaceStyle

so this might do it:

shellscript({defaults read -g AppleInterfaceStyle})="Dark"

Note: I haven’t tested this. Here’s the web page that mentions this technique:

https://stefan.sofa-rockers.org/2018/10/23/macos-dark-mode-terminal-vim/

Yes, that’s what I meant also. But I see on further thought that you would need a function to find out what the current light/dark mode of the system is, and Panorama doesn’t have such a function (revised, see above, maybe this can be done).

It’s amazing to me that 18 months after the release of Mojave, this is the first time dark mode has come up in relation to Panorama. When Mojave was released, I expected to be deluged with requests for dark mode support for Panorama. And there hasn’t been even one! Not on this forum, or in emails. I hesitate to mention this, as this will probably unleash the floodgates.

In spite of their being no requests, this is on my list, I want to support it. I have looked into it some, and it will be very very very complicated to do. Probably at least 2 months of work, perhaps much more. And these issues you’ve brought up in regard to custom menus hadn’t even occurred to me, so that’s going to take even more time. So dark mode support will have to wait in line behind other more high priority projects.

Ah, I see. Hence Live Menu Specification Language, I suppose.

Yes, I was.

Yes, it does. Note that I’m still using Sierra, whose dark mode is limited to the colour of the menu bar and dock, but the same variable is used.

Sorry . . . The implications for the whole UI look rather daunting.

Thanks anyway. That’s all a lot clearer now.