diff --git a/docs/gtk3.tex b/docs/gtk3.tex new file mode 100644 index 0000000000..112042d32f --- /dev/null +++ b/docs/gtk3.tex @@ -0,0 +1,563 @@ +\documentclass[10pt]{article} +\usepackage{a4} +\usepackage{epsfig} +\usepackage{listings} +\usepackage{tabularx} +\lstset{language=Delphi}% +\lstset{basicstyle=\sffamily\small}% +\lstset{commentstyle=\itshape}% +\lstset{keywordstyle=\bfseries}% +%\lstset{blankstring=true}% +\newif\ifpdf +\ifx\pdfoutput\undefined + \pdffalse +\else + \pdfoutput=1 + \pdftrue +\fi +\begin{document} +\title{Programming GTK in Free Pascal:\\ Menus +%and Marshallers +} +\author{Florian Kl\"ampfl\\and\\Micha\"el Van Canneyt} +\date{September 2000} +\maketitle +\section{Introduction} +In the third article on programming the GTK toolkit, the us of menus in GTK +is explored. +%two topics are +%explored: The programming of menus and the use of marshallers. + +Menus can be built in essentially 2 ways; the easier way through the +use of a itemfactory, and the more complex way, doing all necessary calls +manually. The advantages of both ways are discussed. + +%Marshallers can be used to replace the default signal handling mechanisms +%of GTK. The use of marshallers will be demonstrated by building a small +%object which will have custom handlers for certain events. + +\section{Menus the easy way: The item factory} +The easy way to construct a menu is to use an item factory. An Item factory +gets as input an array of records, which describe a menu structure, and +returns a completely built menu, ready to be added to a window. + +The great advantage of an item factory is that it is easy to use; a +disadvantage is that +\begin{enumerate} +\item There is less control over the produced menu items; e.g. +displaying a menu item with a small icon is not possible. +\item The callbacks of the constructed menu is different from the usual +signal model, making it difficult to combine a menu entry with a +speedbutton. There are also 2 types of callback, so type checking is not +possible. +\item In Pascal, constant records must be specified using the names of the +members; this makes the array with the menu items to be rendered quite +complicated. +\end{enumerate} + +To create a menu, first the item factory must be created. The function to do +this is defined as follows: +\begin{lstlisting}{} +gtk_item_factory_new(container_type:TGtkType; + path:Pgchar; + accel_group:PGtkAccelGroup):PGtkItemFactory; +\end{lstlisting} +The three arguments to this function have the following meaning: +\begin{description} +\item[container\_type] This identifies the kind of menu that will be +rendered. It can have one of the following values: +\begin{description} +\item[GTK\_MENU\_BAR\_TYPE] A menu bar will be created to hold all items. +\item[GTK\_MENU\_TYPE] A menu that can be used as a popup menu, or that can be +attached as a sub-menu to another menu, will be created. +\item[GTK\_OPTION\_MENU\_TYPE] Makes everything in a drop-down style menu which +can be used to select one value. +\end{description} +\item[path] is the name of the menu to be generated. +\item[accel\_group] Is a pointer to a group of accelerators. All +accellerators for the generated menu will be attached to this group. +\end{description} + +The accelerator group needed for the item factory can be constructed +using a simple call to \lstinline|gtk_accel_group_new|; this function +takes no arguments, and returns a pointer to a new accelerator group. + +To actually create the menu, a call to +\lstinline|gtk_item_factory_create_items| is needed; This procedure is +defined as follows: +\begin{lstlisting}{} +gtk_item_factory_create_items(ifactory:PGtkItemFactory; + n_entries:guint; + entries:PGtkItemFactoryEntry; + callback_data:gpointer); +\end{lstlisting} +The first argument to this call, \lstinline|ifactory|, is the itemfactory; +the second argument, \lstinline|n_entries|, is the number of items in the +array of records describing the menu. The third argument, \lstinline|entries|, +is the actual array describing the menu. The last argument +\lstinline|callback_data| is a pointer that will be passed to the menu +callbacks. + +The menu structure that should be created by the item factory is an +array of records of the type \lstinline|TGtkItemFactoryEntry|. +This record is defined as follows: +\begin{lstlisting}{} + TGtkItemFactoryEntry = record + path : Pgchar; + accelerator : Pgchar; + callback : TGtkItemFactoryCallback; + callback_action : guint; + item_type : Pgchar; + end; +\end{lstlisting} +The fields have the following meaning: +\begin{description} +\item[path] +The first entry is the path of the menu item. This indicates the place of +the menu entry in the whole menu. For instance, the menu item \textbf{New} +in the menu \textbf{File} would be designated by \lstinline|'/File/New'|. +So, the slash is used to separate the menu levels. + +To make one of the letters of the menu item name active, so the item can be +selected by pressing the letter (on the keyboard) when the menu is opened, +the key to be used should be preceded by an underscore. +In e.g. \lstinline|'/File/_New'|, the letter \textbf{N} could be used to +select the \textbf{New} item if the \textbf{File} menu is active. + +\item[accelerator] To make a shortcut to the menu item so it can be +activated at all times, the shortcut name can be specified in the +\lstinline|accelerator| field. This can be any key, together with some +modifiers. e.g. \lstinline|'<control>N'| will make the key combination +'CTRL-N' a shortcut. + +The accelerator should be speciefied as normal text. A list of possible +modifiers can be found in table \ref{tab:modifiers}. +\begin{table}[ht] +\begin{center} +\caption{List of modifier strings for shortcut keys}\label{tab:modifiers} +\begin{tabular}{cc} +Modifier & alias \\ \hline +\lstinline|<control>| & \lstinline|<ctl>|, \lstinline|<ctrl>| \\ +\lstinline|<shift>| & \lstinline|<shft>| \\ +\lstinline|<alt>| & \lstinline|<mod1>| \\ \hline +\end{tabular} +\end{center} +\end{table} + +\item[callback] Contains a pointer to the function that should be called +when the menu item is activated. The type of the menu handler is not the +same as a normal signal handler; The actual callback should be of the type +\lstinline|TGtkItemFactoryCallback1|: +\begin{lstlisting}{} +procedure (callback_data:gpointer; + callback_action:guint; + widget:PGtkWidget);cdecl; +\end{lstlisting} +Which is not the same as the type of the \lstinline|callback| field, so +a typecast will always be necessary. + +\item[callback\_action] This is passed on to the callback in the +\lstinline|callback_action| parameter. + +\item[item\_type] is the type of menu item. Several types can be used; the +complete list can be found in \ref{tab:menutypes}, but the must important +ones are \lstinline|'<Item>'|, which specifies a normal menu item, + and \lstinline|'<Branch>'|, which indicates a sub-menu. +\begin{table}[ht] +\begin{center} +\caption{Possible menu item types}\label{tab:menutypes} +\begin{tabularx}{\textwidth}{lX}% +Item type & Menu kind \\ \hline +\lstinline|'<Item>'| & indicates a normal item. An empty string or \lstinline|Nil| +have the same meaning. \\ +\lstinline|'<CheckItem>'| & a check menu item. \\ +\lstinline|'<ToggleItem>'| & a toggle menu item (same as check menu). \\ +\lstinline|'<RadioItem>'| & a radio item. \\ +\lstinline|'<Separator>'| & a separator bar. \\ +\lstinline|'<Branch>'| & an item to hold a submenu.\\ +\lstinline|'<LastBranch>'| & an item to hold a submenu, but right aligned.\\ \hline +\end{tabularx} +\end{center} +\end{table} +\end{description} +Now all elements to create a menu are introduced, and the menu can be +created. The following definitions should now be clear: +\begin{lstlisting}{} +Var +Window : PGtkWidget; +MenuBar : PGtkWidget; + +Type + FC = TGtkItemFactoryCallback; + +Const + NrMenuItems = 21; + TheMenu : Array[1..NrMenuItems] of TGtkItemFactoryEntry = ( + (path:'/_File';Accelerator:Nil; + Callback:Nil;Callback_action:1;item_type:'<Branch>'), + (path:'/File/_New';Accelerator:'<ctrl>N'; + Callback:FC(@Menu);Callback_action:1;item_type:Nil), + { ... } +\end{lstlisting} +Here the \lstinline|FC| type is introduced to make the typecast of the +\lstinline|Menu| handler easier; the +\lstinline|TheMenu| constant is not given completely, since it is too long +and not instructive. The complete structure can be found in the sources +accompanying this article. + +Using the above definitions, the menu can now be constructed: +\begin{lstlisting}{} +Procedure MakeMenu; + +Var + Factory : PGtkItemFactory; + Accel : PGtkAccelGroup; + +begin + Accel:=gtk_accel_group_new; + Factory :=gtk_item_factory_new(GTK_MENU_BAR_TYPE,'<main>',accel); + gtk_item_factory_create_items(Factory,NrMenuItems,@TheMenu,Nil); + gtk_window_add_accel_group(GTK_Window(Window),accel); + MenuBar:=gtk_item_factory_get_widget (Factory, '<main>'); +end; +\end{lstlisting} +The \lstinline|gtk_window_add_accel_group| call attaches the accelerator +group that was filled up by the item factory to the window. + +The \lstinline|gtk_item_factory_get_widget| call finally fetches the +object created by the item factory and stores it in a widget variable. + +The \lstinline|Menu| callback used in the menus is defined as follows: +\begin{lstlisting}{} +procedure menu(Data : GPointer; + Action : Guint; + Widget : pGtkWidget); cdecl; + +Var + TheLabel : PgtkWidget; + LabelText : Pchar; + S : AnsiString; + +begin + TheLabel:=g_list_nth_data( + gtk_container_children( + GTK_CONTAINER(Widget)),0); + gtk_label_get(gtk_Label(theLabel),@LabelText); + S := 'Chosen menu : ' + Strpas(Labeltext); + gtk_label_set_text(GTK_LABEL(DisplayLabel),pchar(S)); +end; +\end{lstlisting} +The \lstinline|DisplayLabel| is a label located on the window, it is used to +give some feedback on the used menu. The code to extract the menu name from +the menu widget passed to the handler will be explained later on. + +The result of all this is shown in figure \ref{fig:ex1}. +\begin{figure} +\caption{The menu made by the item factory.}\label{fig:ex1} +\epsfig{file=gtk3ex/ex1.png} +\end{figure} + +As can be seen from the code above, the creation of a menu using an item +factory in GTK is not so hard. The drawback of the above method lies mainly +in the fact that Pascal handles constant records differently than C, which +makes the array that describes the menu structure rather difficult to read. + +The second drawback is that there is little control over the created items. + +\section{Menus the hard way: manually} +When creating menus manually, mainly 4 objects are involved: +\begin{itemize} +\item The menu items themselves. To a menu item, a menu can be assiciated, +creating a sub-menu. +\item Menus, which contain a collection of menu items, +\item A accelerator group. This will be used to keep a collection of +shortcut keys for the menu items. +\item A menu bar, which can hold several menu items and their associated +menus. +\end{itemize} +The last object is optional, if e.g. a pop-up menu is wanted. + +To create a menu in a window, the following steps are involved: +\begin{enumerate} +\item Create an accellerator group. The accelerator group should be +connected to the window. \label{stepone} +\item Create a menu bar, and attach it to the window. +\item For each menu that should appear in a menu bar, do the following: +\begin{itemize} +\item Create a menu item, which will be shown in the menu bar. +\item Create a menu to hold the items that should pop up when the menu is +activated. +\end{itemize} +\item To each menu created in the previous step, add as many menu items as +needed. Add an accelarator to the group created in step \ref{stepone}. +\end{enumerate} + +To make these steps easier (each of them involves quite some calls to GTK +functions) some functions will be introduced that make this easier. + +The first function is the most simple one; it attaches a separator to a +menu: +\begin{lstlisting}{} +Function AddSeparatorToMenu(Menu:PgtkMenu):PgtkMenuItem; + +begin + Result:=pgtkmenuitem(gtk_menu_item_new); + gtk_menu_append(Menu,pgtkWidget(result)); + gtk_widget_show(PgtkWidget(result)); +end; +\end{lstlisting} +The function takes one parameter, \lstinline|Menu|, the menu to which the +separator will be attached. A separator is created by simply creating an +empty menu item. Creating a new (empty) menu item is done with the +\lstinline|gtk_menu_item_new| call. + +With the \lstinline|gtk_menu_append| call, the newly created item is then +added to the menu. Lastly, the item is shown; it will not become actually +visible till the menu is activated. If this is omitted, it will also not +be visible when the menu is activated. + +Adding a menu with a shortcut key to a menu is a little more involved. Some +more elements are needed: +\begin{enumerate} +\item The menu to which to attach the menu item. +\item The accelarator group to which the accelerator key should be added. +\item The caption of the menu. An underscore character will indicate the +letter of themenu that will be used as a shortcut to activate the item. +\item The shortcut for the menu item. An empty string means no shortcut. +\item A callback function which will be called when the menu item is +activated, and callback data which will sent to the callback. +\end{enumerate} +All these elements are found in the declaration of the following function: +\begin{lstlisting}{} +Function AddItemToMenu (Menu : PGtkMenu; + ShortCuts : PGtkAccelGroup; + Caption : AnsiString; + ShortCut : AnsiString; + CallBack : TgtkSignalFunc; + CallBackdata : Pointer + ) : PGtkMenuItem; + +Var + Key,Modifiers : guint; + LocalAccelGroup : PGtkAccelGroup; + TheLabel : PGtkLabel; + +begin +\end{lstlisting} +The variables declared in this function will be explained as the code is +presented. + +First of all, a menu item must be created. Since a caption for the menu is +provided, the \lstinline|gtk_menu_item_new_with_label| will be used to +create a menu that has a label as a child: +\begin{lstlisting}{} + Result:=pgtkmenuitem(gtk_menu_item_new_with_label('')); + TheLabel:=GTK_LABEL(GTK_BIN(Result)^.child); + Key:=gtk_label_parse_uline(TheLabel,Pchar(Caption)); +\end{lstlisting} +After the menu item is created, the child label is fetched. The label caption is +then set using the \lstinline|gtk_label_parse_uline| function. This function +will search a text for underscore characters, remove them from the text, and +then set the label's caption with the result. All letters which had an +underscore character prepended will be underlined in the label. + +The function returns the first letter that had an underscore prepended. It +is stored, so it can be used to make an accelerator: +\begin{lstlisting}{} +If Key<>0 then + begin + LocalAccelGroup:=gtk_menu_ensure_uline_accel_group(Menu); + gtk_widget_add_accelerator(PGtkWidget(result),'activate_item', + LocalAccelGroup,Key, + 0,TGtkAccelFlags(0)); + end; +\end{lstlisting} +The call to \lstinline|gtk_menu_ensure_uline_accel_group| returns the +accelarator group associated with the menu. If no group existed yet, one +will be created. The \lstinline|gtk_widget_add_accelerator| call takes the +following parameters: +\begin{itemize} +\item A pointer to a widget to which the accelerator should be attached. +\item The name of the signal which will be triggered when the shortcut +is activated. +\item The accelerator group to which the shortcut should be installed, +usually this will be the accelerator group for the window to which the +widget is attached, but in this case this is the accelerator group of the +menu (which will only be active when the menu is actually shown) +\item The key from the shortcut. +\item The modifiers that should be pressed together with the key. For the +menu, this should be 0, since just the key should be hit. +\item The accelerator flags. +\end{itemize} + +After the menu item was created and it's underlined key was made into an +accelerator, the menu can be attached to the menu: +\begin{lstlisting}{} +gtk_menu_append(Menu,pgtkWidget(result)); +\end{lstlisting} + +If a shortcut key was passed along to the procedure, can be added to the +window's accelerator group with the following code: +\begin{lstlisting}{} +If (ShortCut<>'') and (ShortCuts<>Nil) then + begin + gtk_accelerator_parse (pchar(ShortCut), @key, @modifiers); + gtk_widget_add_accelerator(PGtkWidget(result),'activate_item', + ShortCuts,Key, + modifiers, GTK_ACCEL_VISIBLE); + end; +\end{lstlisting} +The call to \lstinline|gtk_accelerator_parse| will parse a string which +describes a shortcut key, and returns the corresponding key and modifiers, +which can then be passed on to the \lstinline|gtk_widget_add_accelerator| +call. + +After the accellerator has been installed, the only thing that remains to be +done is to connect the callback to the activation of the menu: +\begin{lstlisting}{} +If CallBack<>Nil then + gtk_signal_connect(PGtkObject(result),'activate', + CallBack,CallBackdata); +gtk_widget_show(PgtkWidget(result)); +end; +\end{lstlisting} +As the last line in the procedure, the newly created menu item is shown. +If the menu isn't visible yet, this will do nothing, but will ensure that +the item is also visible when the menu is visible. + +Now a menu-item and a separator can be added to a menu. What remains to be +done is to add a menu to a menu bar. This is done in the following +procedure, which is given in its entirety: +\begin{lstlisting}{} +Function AddMenuToMenuBar(MenuBar : PGtkMenuBar; + ShortCuts : PGtkAccelGroup; + Caption : AnsiString; + CallBack : TgtkSignalFunc; + CallBackdata : Pointer; + AlignRight : Boolean; + Var MenuItem : PgtkMenuItem + ) : PGtkMenu; + +Var + Key : guint; + TheLabel : PGtkLabel; + +begin + MenuItem:=pgtkmenuitem(gtk_menu_item_new_with_label('')); + If AlignRight Then + gtk_menu_item_right_justify(MenuItem); + TheLabel:=GTK_LABEL(GTK_BIN(MenuItem)^.child); + Key:=gtk_label_parse_uline(TheLabel,Pchar(Caption)); + If Key<>0 then + gtk_widget_add_accelerator(PGtkWidget(MenuItem),'activate_item', + Shortcuts,Key, + GDK_MOD1_MASK,GTK_ACCEL_LOCKED); + Result:=PGtkMenu(gtk_menu_new); + If CallBack<>Nil then + gtk_signal_connect(PGtkObject(result),'activate', + CallBack,CallBackdata); + gtk_widget_show(PgtkWidget(MenuItem)); + gtk_menu_item_set_submenu(MenuItem, PgtkWidget(Result)); + gtk_menu_bar_append(MenuBar,PgtkWidget(MenuItem)); +\end{lstlisting} +The code for this procedure quite similar as the previous one. The main +differences are: +\begin{itemize} +\item The result is not a menuitem, but a whole menu. The menuitem that is +displayed in the menu bar itself is returned in the \lstinline|MenuItem| +parameter. +\item The shortcut key for the underlined item is added to the window's +accelerator group, and has the \textsc{Alt} key (or \textsf{Mod1}) as +the modifier key. +\item the created menu is attached to the menu item as a sub menu, and it is +the menu-item which is attached to the menu bar. +\end{itemize} + +With the above calls, a menu can be constructed with a simple set of calls: +\begin{lstlisting}{} +FileMenu:=AddMenuToMenuBar(MenuBar,accel,'_File',Nil, + Nil,False,TempMenuItem); +AddItemToMenu(FileMenu,accel,'_New','<control>N', + TgtkSignalFunc(@menu),DisplayLabel); +AddItemToMenu(FileMenu,accel,'_Open','<control>O', + TgtkSignalFunc(@menu),DisplayLabel); +AddItemToMenu(FileMenu,accel,'_Save','<control>S', + TgtkSignalFunc(@menu),DisplayLabel); +AddSeparatorToMenu(PGtkMenu(FileMenu)); +AddItemToMenu(FileMenu,accel,'_Quit','<control>Q', + TgtkSignalFunc(@destroy),Nil); +{ ... } +\end{lstlisting} +The complete list of calls to create the menu can be found in the sources +accompagnying this article. + +The second program is of course bigger than the first, due to all the code +to create the menus. Nevertheless, the manual way of creating has it's +advantages: it's quite easy to extend the AddItemToMenu to add a bitmap to +the menu entry as well. Using a itemfactory, there is (currently) no way to +add images to a menu. + +Adding a bitmap to a menu is quite easy, and requires only a few extra +lines of code. The key point is that the gtkmenuitem object is just an empty +container (it descends from gtkbin), which does not display anything by itself. The +\lstinline|gtk_menu_item_new_with_label| call creates a menu item and puts a +gtklabel in it to display the menu item caption. Instead of a label object, +almost any other object can be put in the item. This fact is used in the +following code to add a bitmap in front of the menu caption, in a new +procedure to be called \lstinline|AddImageItemToMenu|: +\begin{lstlisting}{} + Result:=pgtkmenuitem(gtk_menu_item_new); + hbox:=PGtkHBox(gtk_hbox_new(false,0)); + gtk_container_add(pgtkcontainer(result),pgtkWidget(hbox)); + pixmap:=gdk_pixmap_create_from_xpm(Nil,@BitmapData,Nil,pchar(BitMap)); + Image := PgtkPixMap(gtk_pixmap_new(Pixmap,BitmapData)); + gtk_box_pack_start(PGtkBox(hbox),pgtkWidget(image),false,false,0); + TheLabel:=PgtkLabel(gtk_label_new('')); + gtk_box_pack_start(PGtkBox(hbox),pgtkWidget(TheLabel),True,True,0); + Key:=gtk_label_parse_uline(TheLabel,Pchar(Caption)); +\end{lstlisting} +In the first line, a plain menu item is created with +\lstinline|gtk_menu_item_new|. In the following two lines, +a \lstinline|GTKHBox| is added to the menu item, and a reference to the box +is stored in the \lstinline|hbox| variable. + +Then, a pixmap is created from a filename. The filename is passed in the +\lstinline|BitMap| parameter to our routine. Using the newly created pixmap, +an Image is created, which can then be added to the box. + +Finally, a regular GTK label is created to hold the caption of the menu +item, and added to the box. After that the procedure continues as for a +normal menu. + +The complete code for the above \lstinline|AddImageItemToMenu| routine can +be found in the sources of the third example, accompagnying this article. +The result can be seen in figure \ref{fig:pixmenu} +\begin{figure}[ht] +\caption{The menu with bitmaps}\label{fig:pixmenu} +\epsfig{file=gtk3ex/ex3.png} +\end{figure} + +Some notes regarding this algorithm are in order: +\begin{enumerate} +\item It would be possible to have not a filename passed to the routine, but +directly pass a pixmap object as well; for instance when using a toolbar, +toolbuttons corresponding to the menu entries could share the same pixmaps +as the menu entries. +\item Some alignment issues may arise when the menu contains items with and +without bitmaps. The above code does not address these issues. To solve +them, the regular menu items should also be constructed e.g. using a hbox or a +table with an empty cell. Also, an algorithm to determine whether any item of +the menu has an image would be needed. +\item The shortcut key is no longer shown in the menu widget; The reason for +this is unknown to the authors of this article; unfortunately the lack of +documentation on GTK prevents the implementation of a remedy. +\item The menu callback can no longer retrieve the menu text using a +straightforward approach, since the label displaying the caption is +no longer the only child widget of the menu item. The callback has been +adapted for this in the example. +\end{enumerate} +Taking into account the above arguments should make it possible to write +better menu-creating routines which would replace the item factory +completely, and which would enable the use of bitmaps in menu items. + +\end{document}