Mon Jul 17 22:05:08 CEST 2006

Carbon menus

This is the second blog entry in mini-serie about native menus for OpenOffice.org.

Today, I'll describe how menus are created and displayed in my sample Carbon application. I rewrote the example so it matches better the OpenOffice.org native menus API.

menus File and Edit in Carbon


I'll now describe, how the above image can be generated using Carbon instead of Gimp ;-) Let's start with the small description of the terms menubar and menu. Please read the article The Menu Bar and Its Menus in the Apple Human Interface Guidelines which describes them very well.

As you can see on the image above, we have defined two menus inside application menu - menu File and menu Edit. They are contained in the invisible "RootMenu" which is a container for them. In OpenOffice.org, we will use one RootMenu for every OpenOffice.org window.

So how can we create the RootMenu?
MenuRef rootMenu;
CreateNewMenu(0, 0, &rootMenu);
It is very simple - we will call CreateNewMenu function and the new menu will be returned in the variable rootMenu. We already know OpenOffice.org native menus API, so we can see RootMenu as an equivalent to SalMenu created with bMenuBar = TRUE. This means that our class AquaSalMenu will add new member to reference the RootMenu's MenuRef and the function AquaSalInstance::CreateMenu will set it when called with bMenuBar = TRUE to create the menu bar (and will set its bMenuBar member to TRUE to reflect it is a menubar).

So we have a menu bar now. The menu bar is not shown yet, we will actually display it later. Now we can create the actual menus to be attached to the menu bar (RootMenu):
MenuRef myMenu;
CreateNewMenu(1, 0, &myMenu);
Actual menus are created using the same function as the menu bar (RootMenu) itself. Yes, it's true - we will see later what is the difference (hint: calling the function SetRootMenu). Every top-level menu is represented using its MenuRef reference. As the same applies to MenuBar itself (it is AquaSalMenu too), we can share the same member for MenuRef of the menu bar and menu and use bMenuBar member to distinguish between menus and menu bars.

Now, I'll describe the first main difference between both APIs. In OpenOffice.org native menu API, the string "File" is associated to the empty member item in the MenuBar. In Carbon, "File" is the title of the menu and is associated with it, not with the menu bar itself:
SetMenuTitleWithCFString( myMenu, CFSTR("File") );
So now, the menu created above is named as "File", it is still empty and still not shown. So we can fill it with items:
MenuItemIndex item;
AppendMenuItemTextWithCFString( myMenu, CFSTR("Open"),  0, 0, &item );
AppendMenuItemTextWithCFString( myMenu, CFSTR("Close"), 0, 0, &item );
AppendMenuItemTextWithCFString( myMenu, CFSTR("Exit"),  0, 0, &item );
We have just added three menu items (they will be mapped directly to AquaSalMenuItems created using AquaSalInstance::CreateMenuItem) to the menu. The returned value in the variable item together with the MenuRef myMenu will be stored for future reference (have you already noticed that our menu items are not functional, they are only items in menus? ;-).

Now that we have menu "completely" defined, we can add it into the menu bar (link it with the menu bar). We will first insert unnamed empty item into the menu bar and then will set its hierarchical menu to the just defined "File" menu. This is different in OpenOffice.org native menus API, because in OpenOffice.org, you insert menu item "File" into menu bar and then you SetPopupMenu of it to the File menu which is untitled (it only contains menu-items "Open", "Close", ...):
MenuItemIndex myMenuIndex;
AppendMenuItemTextWithCFString( rootMenu, NULL, 0, 0, &myMenuIndex );
SetMenuItemHierarchicalMenu(rootMenu, myMenuIndex, myMenu);
The function AppendMenuItemTextWithCFString will create empty menu item in the menu bar and the function SetMenuItemHierarchicalMenu will set its hierarchical menu to our "File" menu defined above.

Now we can do the same with the "Edit" menu:
CreateNewMenu(1, 0, &myMenu);
SetMenuTitleWithCFString( myMenu, CFSTR("Edit") );
AppendMenuItemTextWithCFString( myMenu, CFSTR("Search"), 0, 0, &item );
AppendMenuItemTextWithCFString( myMenu, CFSTR("Replace"), 0, 0, &item );
AppendMenuItemTextWithCFString( rootMenu, NULL, 0, 0, &myMenuIndex );
SetMenuItemHierarchicalMenu(rootMenu, myMenuIndex, myMenu);
So now we have the menu bar defined, two menus connected to it, but it still is not displayed. This way, we can define more menu bars, but we still have to set one of them to be displayed. This is what SetRootMenu is doing:
SetRootMenu(rootMenu);
This function sets the RootMenu's part of the application menu to the menu referenced by its MenuRef. We will have to call this function when changing windows (frames) because of different menus in different windows. This also means that we have to extend AquaSalFrame class to contain also its RootMenu's MenuRef.

This entry was written to make it clear how menus in OpenOffice.org and menus in Carbon differ. I have not yet investigated how to pass menu selection for processing, how separators work, how menu shortcuts work etc. This is not the goal of this blog entry. My first goal is to have the menus actually displayed first. I hope you found it interesting to read it. If so, please drop me a line. I hope I (or Pierre) will find the time this week to make this into the actual code for you to test! Please bear with us. I'm no Carbon hacker. I'm learning on the go. BTW, can you help us?

Posted by Pavel | Permanent link | File under: OpenOffice.org, Mac OS X