Using Menus in Xt - THE.UNIX.WORLD
Pubblicato da mammon_ il 06/1999
Livello intermedio

Introduzione

/* Questo articolo e' stato tratto dall'Assembly Programming Journal numero 4 */

::/ \::::::.
:/___\:::::::.
/|    \::::::::.
:|   _/\:::::::::.
:|  _|\ \::::::::::.
:::\_____\:::::::::::............................................THE.UNIX.WORLD

Iniziamo

A simple one-button application will not get you very far in the X world. A good application must have multiple components, including menus, dialog boxes, application windows, and accelerators. In this article I will present the use of Xt menus in assembly language as a first step towards producing a complete application; future articles will cover the integration of forms, dialog boxes, and other resources into a complete application.
I presented some Xt macros for Nasm last issue, but have revised them after a bit more testing. The CALLBACK macros have been rewritten, and an InitXtApp macro has been written as well to take care of the generic Xt application startup code.


;=======================================================================-xt.inc

%macro InitXtApp 5
;------Usage: InitXtApp TopLevelWidget, Context, ClassName, ARGC, ARGV
EXTERN XtVaAppInitialize
    push dword 0
    push dword 0
    push dword 0
    push dword %5
    push dword %4
    push dword 0
    push dword 0
    push dword %3
    push dword %2
    call XtVaAppInitialize
    add esp, 36
    mov [%1], eax
%endmacro

%macro CALLBACK 1
;------Usage: CALLBACK name
GLOBAL %1
%1:
%define CallData     [ebp+8]
%define ClientData     [ebp+12]
%define Widget        [ebp+16]
    push ebp
    mov ebp, esp
%endmacro

%macro CALLBACK_RETURN 0
;------Usage: CALLBACK_RETURN
    mov esp, ebp
    pop ebp
    ret
%endmacro

%macro ShowXtWidget 1
;------Usage: ShowXtWidget Widget
EXTERN XtRealizeWidget
    push dword [%1]
    call XtRealizeWidget
    add esp, 4
%endmacro

%macro XtAppLoop 1
;------Usage: XtAppLoop Context
EXTERN exit
EXTERN XtAppMainLoop
    push dword [%1]
    call XtAppMainLoop
    add esp, 4
    push dword 0
    call exit
%endmacro
;==============================================================================
The first thing that must be done in creating an application menu is to create a "menubar" that will contain the component menu buttons. This often takes the place of a Box or Form widget that resides on the top or left edge of an application. The menu bar will later be "filled" with a button for each menu [e.g. File, Edit, Options, Help].
I have opted to use the Box widget for the menubar as it requires less initialization than the Form widget; normally it defaults to a vertical stacking of the component buttons, but this can be fixed with further configuration and will be addresses in a future article.
To create a menubar widget I have create a macro which will simply create a Box widget; it takes as its parameters a pointer to the parent widget [usually the top-level widget], the name of the menubar class, and a dword variable which will become a pointer to the Box instance:

;=======================================================================-xt.inc

%macro CreateMenuBar 3
;------Usage: CreateMenuBar ParentWidget, ClassName, MenubarWidget
EXTERN boxWidgetClass
EXTERN XtCreateManagedWidget
    push dword 0
    push dword 0
    push dword [%1]
    push dword [boxWidgetClass]
    push dword %2
    call XtCreateManagedWidget
    mov [%3], eax
    add esp, 20
%endmacro

;==============================================================================
Following this, a button must be created for each menu as a component of the Box or menubar widget. Each button is then associated with a Menu widget through the XtCreatePopupShell call.
In Xt Athena, a menu is a button which, when pressed, creates a "Popup Shell"; that is, a container which contains the various menu items. These items are known as smeObjectClasses, or "simple menu entry" objects; selecting a simple menu entry from the popup shell [in plain English, "selecting a menu item"] will cause the callback for that menu item to be invoked and any data associated with the menu to be sent to the callback.
The AddMenu macro will create a menu button and popup shell for a given menu; it takes as its parameters the pointer to the parent Menubar widget, the ASCII string to be displayed in the menu [e.g. "File", "Edit", etc], a dword variable to be filled with a pointer to the menu button, and a dword variable to be filled with a pointer to the menu [or "popup shell"] itself:

;=======================================================================-xt.inc

%macro AddMenu 4
;------Usage: AddMenu MenubarWidget, MenuText, MenuButtonWidget, MenuWidget
%ifndef _menu_classname
EXTERN menuButtonWidgetClass
EXTERN XtCreateManagedWidget
EXTERN simpleMenuWidgetClass
EXTERN XtCreatePopupShell
[section .data]
_menu_class    db    "menu",0
[section .text]
%define _menu_classname
%endif
    push dword 0
    push dword 0
    push dword [%1]
    push dword [menuButtonWidgetClass]
    push dword %2
    call XtCreateManagedWidget
    mov [%3], eax
    add esp, 20

    push dword 0
    push dword 0
    push dword [%3]
    push dword [simpleMenuWidgetClass]
    push dword _menu_class
    call XtCreatePopupShell
    mov [%4], eax
    add esp, 20
%endmacro
;==============================================================================
Once the menu is created, it must be filled with menu items. The standard menu item is an smeBSBObject, meaning "simple menu entry:
Bitmap-String-Bitmap"; that is, each line can consist of a bitmap followed by a string followed by a bitmap. This can be useful for providing "visual"/eyecandy menus, or for checking and unchecking [via an "x" bitmap] menu items. For this example, I am using only string menu entries.
Menu items are added with the standard CreateManagedWidget function, then associated with a callback routine just like standard Xt widgets. The AddMenuItem macro performs both of these functions and takes as its parameters the pointer to the parent Menu, the text to be displayed in the menu item, a pointer to the data associated with the menu item, and the address of the callback routine:

;=======================================================================-xt.inc

%macro AddMenuItem 4
;------Usage: AddMenuItem MenuWidget, MenuItemText, MenuItemID, Callback
%ifndef menu_entry_ptr
EXTERN smeBSBObjectClass
EXTERN XtCreateManagedWidget
EXTERN XtAddCallback
[section .data]
MenuEntry:
.ptr        dd    0
_callback_type    db    "callback",0
[section .text]
%define menu_entry_ptr
%endif
    push dword 0
    push dword 0
    push dword [%1.ptr]
    push dword [smeBSBObjectClass]
    push dword %2
    call XtCreateManagedWidget
    mov [MenuEntry.ptr], eax
    add esp, 20

    push dword %3
    push dword %4
    push dword _callback_type
    push dword [MenuEntry.ptr]
    call XtAddCallback
    add esp, 16
%endmacro
;==============================================================================
Note that the pointer to the Menu Item Entry widget is needed only to install the callback, and can then be discarded.
I have also included a separator menu item to break up the menus a bit; this is simply a menu item of class smeLineObject which does nothing; as such, the AddMenuSeparator macro requires only the pointer to the parent Menu as a parameter:

;=======================================================================-xt.inc

%macro AddMenuSeparator 1
;------Usage: AddMenuSeparator MenuWidget
%ifndef _szseperator
EXTERN smeLineObjectClass
EXTERN XtCreateManagedWidget
[section .data]
_szSep    db    "line",0
[section .text]
%define _szseperator
%endif
    push dword 0
    push dword 0
    push dword [%1.ptr]
    push dword [smeLineObjectClass]
    push dword _szSep
    call XtCreateManagedWidget
    mov [MenuEntry.ptr], eax
    add esp, 20
%endmacro
;==============================================================================
Why so many macros? To ease the tedium. The Xt code presented below was many pages longer before I converted the menu creation routines --which run 10 to 20 lines apiece-- into macros. Also, looking at the sample application, you will see that all of the "generic" Xt code has been compressed to a few lines, leaving the bulk of the "real code" in the callback routine and in the resource definitions.
I have chosen to use a single callback for all of the menu items; this is the most simple and perhaps the most efficient method. Each menu entry has a unique integer ID which is passed to the callback as data when the menu item is selected; the callback compares its incoming ClientData variable with each of the menu entry IDs until it finds a match, whereupon it jumps to a specific subroutine for each different ID. In short, a typical message handler -- though I refrained from my notorious call-table and SWITCH/CASE macros in this case for the sake of clarity.
The structure of the Widget declarations in this file could also bear some discussion. I have chosen to treat all widgets declared in the application [*not* references to the external widget classes reference by the app, but rather the pointers to those classes] as primitive structures of the following form:

WidgetInstance:
.ptr dd 0
.name db 'name',0
This makes things easier, for the widget pointer can now be referred to as [WidgetInstance.ptr], and the widget name can be referred to as WidgetInstance.name. Notice that all widget pointers must be referenced in brackets; the typical C use of a pointer is to pass the address contained in the pointer, not the address of the pointer itself [unless the pointer is dereferenced by a "&", in which case the brackets should not be used in the assembly language equivalent].
The menu widgets have a similar, but more advanced syntax:

MenuName:
.ptr
.name
.button
.Entryname
.EntrynameID
This allows the pointer for each menu button to be stored along with the rest of the menu data, and also allows menu entries to be referenced as "members" of the menu "structure", e.g. MenuName.Entryname would be the text of the menu item, and MenuName.EntrynameID would be the integer ID for the menu item.
Now for the actual implementation:

;===================================================================-xtmenu.asm

BITS 32
EXTERN exit
EXTERN printf
%include "Xt.inc"
;==========================================================================DATA

[section .data]
;______________Widgets__________________________ 
Shell:
.ptr        dd    0
.name        db    "shell",0
MenuBar:
.ptr        dd    0
.name        db    "menubar",0

;_______________Menus___________________________ 
FileMenu:
.ptr        dd    0
.name        db    "File",0
.button        dd    0
.New        db    "New",0
.NewID        dd    "101"
.Open        db    "Open",0
.OpenID        dd    "102"
.Save        db    "Save",0
.SaveID        dd    "103"
.Close        db    "Close",0
.CloseID     dd    104
.Exit        db    "Exit",0
.ExitID        dd    105

HelpMenu:
.ptr        dd    0
.name        db    "Help",0
.button        dd    0
.About        db    "About",0
.AboutID     dd    201

;____Classes____________________________________ 
XtMenu:        db     "XtMenu",0

;____Misc_______________________________________ 
ARGC:    times 128 db    0
szOutString    db    "%s selected",0ah,0dh,0
AppContext    dd    0

;==========================================================================CODE

[section .text]
CALLBACK CBMenuSelect
    mov ebx, ClientData
    mov eax, [ebx]
    cmp eax, dword [FileMenu.NewID]
    je MenuNew
    cmp eax, dword [FileMenu.OpenID]
    je MenuOpen
    cmp eax, dword [FileMenu.SaveID]
    je MenuSave
    cmp eax, dword [FileMenu.CloseID]
    je MenuClose
    cmp eax, dword [FileMenu.ExitID]
    je MenuExit
    cmp eax, dword [HelpMenu.AboutID]
    je MenuAbout
    jmp unhandled
MenuNew:
    push dword FileMenu.New
    jmp do_printf
MenuOpen:
    push dword FileMenu.Open
    jmp do_printf
MenuSave:
    push dword FileMenu.Save
    jmp do_printf
MenuClose:
    push dword FileMenu.Close
    jmp do_printf
MenuAbout:
    push dword HelpMenu.About
do_printf:
    push dword szOutString
    call printf
    add esp, 8
unhandled:
    CALLBACK_RETURN
MenuExit:
    mov esp, ebp
    pop ebp
    push dword 0
    call exit
    ret
;____________________________________________________________PROGRAM_ENTRYPOINT

GLOBAL main
main:
InitXtApp Shell.ptr, AppContext, XtMenu, ARGC, 0

;-------Make Menu
    CreateMenuBar Shell.ptr, MenuBar.name, MenuBar.ptr
    AddMenu MenuBar.ptr, FileMenu.name,FileMenu.button, FileMenu.ptr
    AddMenu MenuBar.ptr, HelpMenu.name,HelpMenu.button, HelpMenu.ptr

;-------Build File Menu
    AddMenuItem FileMenu, FileMenu.New, FileMenu.NewID, CBMenuSelect
    AddMenuItem FileMenu, FileMenu.Open, FileMenu.OpenID, CBMenuSelect
    AddMenuItem FileMenu, FileMenu.Save, FileMenu.SaveID, CBMenuSelect
    AddMenuItem FileMenu, FileMenu.Close, FileMenu.CloseID, CBMenuSelect
AddMenuSeparator FileMenu
    AddMenuItem FileMenu, FileMenu.Exit, FileMenu.ExitID, CBMenuSelect
;-------Build Help Menu
    AddMenuItem HelpMenu, HelpMenu.About, HelpMenu.AboutID, CBMenuSelect

;-------Show Top-Level Widget
    ShowXtWidget Shell.ptr
;-------Enter Xt Application Loop
    XtAppLoop AppContext

;___Compile_Strings_________________________________________
; nasm -f elf xtmenu.asm
; gcc -o xtmenu xtmenu.o -lXaw -lXt -lX11 -L/usr/X11R6/lib
;==========================================================================-EOF
The strings needed to compile and link Xt assembly apps are provided at the end of the file. Note once again how short the main application code is; the menu data could easily be moved into an xtmenu.inc file and thereby trim the "apparent size" of the application down to the initialization code and the callback.

Conclusioni

Further automation could involve creating a "resource editor" which would produce .INC files compatible with the Xt.inc macros, as well as creating high-level calls to automatically adjust the stack and high-level structures to deal with message handling in the callback.