\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}% \newcommand{\file}[1]{\textsf{#1}} \usepackage[pdftex]{hyperref} \newif\ifpdf \ifx\pdfoutput\undefined \pdffalse \else \pdfoutput=1 \pdftrue \fi \begin{document} \title{Programming GTK in Free Pascal: Making a real-world application.} \author{Florian Kl\"ampfl\\and\\Micha\"el Van Canneyt} \date{January 2001} \maketitle \section{Introduction} In the third article on programming the GTK toolkit, the use of several GTK widgets is demonstrated by building a real-world application. The main widgets to be shown are the Toolbar, CList and Tree widgets. Along the way, some other widgets such as a dialog will be shown as well. The program to show all this will be a small file explorer. It will not perform all functions that one would expect from a file explorer, but it is not meant to be, either. It just demonstrates how one could go about when making a file explorer. The File explorer will have 2 main components. One is a directory tree which can be used to select a directory. the other is a Clist, a component that presents a list of items in a table with headings. The Clist will be used to display the files in the directory selected in the directory tree. The functionality included will be limited to viewing the properties of a file, and deleting a file. The view can be customized, and sorting of columns by clicking the column header is possible. Each window developed in the article will be described in a record, i.e. all window elements will have a field in a record that points to the GTK widget used. Several forms will be developed, and each form will be put in a separate unit. Signal callbacks will in general receive a 'userdata' pointer that points to the window record. This approach mimics the object oriented approach of GTK, and is similar to the approach in Delphi, where instead of a object, a window class is used. \section{The main window} The main window will consist of a menu, a tool bar, a directory tree and the file list. The bottom of the screen will contain a statusbar. Between the directory tree and the file list is a splitter that can be used to resize the directory tree. Right-clicking on the file list will show a popup menu, from which file actions can be selected. All the widgets in the main window will be stored in a big record \lstinline|TMainWindow|: \begin{lstlisting}{} TMainWindow = Record FDir, FMask : String; Window : PGtkWindow; Menu : PGtkMenuBar; Toolbar : PGtkToolBar; DirTree : PGtkTree; FileList : PGtkClist; Pane : PGtkPaned; StatusBar : PGtkStatusBar; FilesHeader,DirHeader : PGtkLabel; // helper objects - Menu Accel : PGtkAccelGroup; MFile, MView, MColumns, MHelp, // Main menu items PMFiles : PGtkMenu; MIFile, MIFileProperties, MIFileDelete, MIExit, MiColumns, MIAbout, MIHelp : PGtkMenuItem; MIShowTitles, MIShowExt, MIShowSize, MiShowDate, MIShowAttrs : PGtkCheckMenuItem; // Files PopupMenu Items: PMIFileProperties, PMIFileDelete : PGtkMenuItem; // Packing boxes VBox, LeftBox, RightBox : PGtkBox; // Scroll boxes TreeScrollWindow, ListScrollWindow : PGtkScrolledWindow; // Tree root node. RootNode : PGtkTreeItem; end; PMainWindow = ^TMainWindow; \end{lstlisting} The record resembles a form class definition as used in \lstinline|Delphi|, it contains all possible widgets shown on the window. The most important ones are of course the \lstinline|DirTree| and \lstinline|FileList| fields, the \lstinline|Menu| which will refer to the main menu and the \lstinline|PMfiles| which will hold the popup menu. The Status bar is of course in the \lstinline|StatusBar| field, and the \lstinline|ToolBar| field will hold the main toolbar of the application. The \lstinline|FDir| field will be used to hold the currently shown directory and the \lstinline|FMask| field can be used to store a file mask that determines what files will be shown in the list. All these fields are filled in using the function \lstinline|NewMainForm| : \begin{lstlisting}{} Function NewMainForm : PMainWindow; \end{lstlisting} The function starts as follows : \begin{lstlisting}{} begin Result:=New(PMainWindow); With Result^ do begin FMask:='*.*'; Window:=PgtkWindow(gtk_window_new(GTK_WINDOW_TOPLEVEL)); gtk_window_set_title(Window,SFileExplorer); gtk_widget_set_usize(PgtkWidget(Window),640,480); gtk_signal_connect (PGTKOBJECT (window), 'destroy', GTK_SIGNAL_FUNC (@destroy), Result); gtk_widget_realize(PgtkWidget(window)); \end{lstlisting} This is a more or less standard GTK setup for a window. Note that the pointer to the window record is passed to the 'destroy' signal handler for the window, and that the window widget is realized (so a actual window is created). The necessity for the 'realize' call is explained below. After the window is created, the main widgets on the form are created: \begin{lstlisting}{} Menu:=NewMainMenu(Result); ToolBar:=NewToolbar(Result); StatusBar:=PgtkStatusBar(gtk_statusbar_new); FileList:=NewFileList(Result); DirTree:=NewDirtree(Result); PMFiles:=NewFilePopupMenu(Result); \end{lstlisting} The functions used to create these widgets will be discussed further on. \begin{description} \item[Menu] The menu is created in the function \lstinline|NewMainMenu| \item[ToolBar] The toolbar is created in the \lstinline|NewToolbar| function. \item[FileList] The CList component which will show the file data. Created using \lstinline|NewFileList|. \item[DirTree] The directory tree showing the directory structure of the disk is created using \lstinline|NewDirtree|. \item[PMFiles] is the popup menu for the file list and is created in the \lstinline|NewFilePopupMenu| function. \end{description} Each function will set the fields which contain the helper widgets. After the main widgets have been created, it is time to put them on the form, and the rest of the \lstinline|NewMainForm| function is concerned mainly with placing the widgets in appropriate containers. A splitter widget in GTK is called a \lstinline|paned window|. It can be created using one of the following functions: \begin{lstlisting}{} function gtk_hpaned_new : PGtkWidget; function gtk_vpaned_new : PGtkWidget; \end{lstlisting} Since the directory tree and file explorer window will be located left to each other, a \lstinline|gtk_hpaned_new| call is needed for the file explorer. The \lstinline|paned window| has 2 halves, in each of which a widget can be placed. This is done using the following calls: \begin{lstlisting}{} procedure gtk_paned_add1(paned:PGtkPaned; child:PGtkWidget);cdecl; procedure gtk_paned_add2(paned:PGtkPaned; child:PGtkWidget);cdecl; \end{lstlisting} The first function adds a widget to the left pane, the second to the right pane (or the top and bottom panes if the splitter is vertical). With this knowledge, the Directory Tree and File List can be put on the form. In the case of the file explorer, 2 widgets will be packed in vertical boxes which are on their turn put the left and right panes of the splitter: \begin{lstlisting}{} Pane:=PgtkPaned(gtk_hpaned_new); DirHeader:=PgtkLabel(gtk_label_new(pchar(SDirTree))); LeftBox:=PGtkBox(gtk_vbox_new(false,0)); gtk_box_pack_start(Leftbox,PGtkWidget(DirHeader),False,False,0); gtk_box_pack_start(Leftbox,PgtkWidget(TreeScrollWindow),true,True,0); gtk_paned_add1(pane,PGtkWidget(Leftbox)); \end{lstlisting} The left-hand side vertical box (\lstinline|LeftBox|) contains a label (\lstinline|DirHeader|) which serves as a heading for the directory tree (\lstinline|DirTree|). It displays a static text (in the constant \lstinline|SDirTree|). The right pane can be filled in a similar way with the file list: \begin{lstlisting}{} FilesHeader:=PgtkLabel(gtk_label_new(pchar(SFilesInDir))); RightBox:=PGtkBox(gtk_vbox_new(false,0)); gtk_box_pack_start(Rightbox,PGtkWidget(FilesHeader),False,False,0); gtk_box_pack_start(Rightbox,PGtkWidget(ListScrollWindow),true,True,0); gtk_paned_add2(pane,PGtkWidget(Rightbox)); \end{lstlisting} The right-hand side vertical box contains a label \lstinline|FileHeader| which serves as a heading for the file list (\lstinline|FileList|). It will be used to display the current directory name (\lstinline|SFilesInDir| constant). After the directory tree and file view have been put in a paned window, all that is left to do is to stack the statusbar, paned window, toolbar and menu in a vertical box \lstinline|VBox| which covers the whole window: \begin{lstlisting}{} VBox:=PGtkBox(gtk_vbox_new(false,0)); gtk_container_add(PGtkContainer(Window),PgtkWidget(VBox)); gtk_box_pack_start(vbox,PGtkWidget(Menu),False,False,0); gtk_box_pack_start(vbox,PGtkWidget(ToolBar),False,False,0); gtk_box_pack_start(vbox,PGtkWidget(Pane),true,true,0); gtk_box_pack_start(vbox,PGtkWidget(StatusBar),false,false,0); gtk_widget_show_all(PGtkWidget(vbox)); end; end; \end{lstlisting} The destroy signal of the window does nothing except destroying the main window record and telling GTK to exit the event loop: \begin{lstlisting}{} procedure destroy(widget : pGtkWidget ; Window : PMainWindow); cdecl; begin gtk_clist_clear(Window^.FileList); dispose(Window); gtk_main_quit(); end; \end{lstlisting} The call to \lstinline|gtk_clist_clear| serves to clear the file list window. The necessity for this call will be explained below. \section{The file list} The file list is constructed using the GTK CList widget. This is a powerful widget that contains a lot of functionality, comparable to the \lstinline|TListView| component found in Delphi. A the file list widget is created using the following function: \begin{lstlisting}{} Function NewFileList(MainWindow : PMainWindow) : PGtkClist; Const Titles : Array[1..6] of pchar = ('Name','ext','Size','Date','Attributes',''); begin MainWindow^.ListScrollWindow:= PGtkScrolledWindow(gtk_scrolled_window_new(Nil,Nil)); gtk_scrolled_window_set_policy(MainWindow^.ListScrollWindow, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); Result:=PGtkClist(Gtk_Clist_New_with_titles(6,@Titles)); gtk_Container_add(PGTKContainer(MainWindow^.ListScrollWindow), PGtkWidget(Result)); \end{lstlisting} A Clist object is not capable of drawing scroll bars if it contains too many items for its size, so first a \lstinline|Scrolled Window| is created in which the Clist object is embedded. A scrolled window is a container widget which does nothing except providing scrollbars for the widget it contains. A scrolled window is created using the \lstinline|gtk_scrolled_window_new| function: \begin{lstlisting}{} function gtk_scrolled_window_new(hadjustment:PGtkAdjustment; vadjustment:PGtkAdjustment):PGtkWidget \end{lstlisting} The \lstinline|Adjustment| parameters can be used to pass scrollbar widgets that the scrolled window should use to do it's work. If none are passed, the scrolled window will create the needed scrollbars by itself. The visibility of the scrollbars can be controlled with the policy property of the scrolled window: \begin{lstlisting}{} gtk_scrolled_window_set_policy(scrolled_window:PGtkScrolledWindow; hscrollbar_policy:TGtkPolicyType; vscrollbar_policy:TGtkPolicyType) \end{lstlisting} The horizontal and vertical policies can be set to the following values: \begin{description} \item[GTK\_POLICY\_AUTOMATIC] Scrollbars are only visible if they are needed. \item[GTK\_POLICY\_ALWAYS] Scrollbars are always visible. \end{description} After the creation of the scrolled window, the file list is created and added to the scrolled window. A CList widget can be created using 2 calls; \begin{lstlisting}{} function gtk_clist_new (columns:gint):PGtkWidget; function gtk_clist_new_with_titles (columns:gint; titles:PPgchar):PGtkWidget; \end{lstlisting} In both cases, the number of columns in the list must be passed. If the column header titles are fixed and known, they can be passed in the \lstinline|gtk_clist_new_with_titles| call, but they can still be set and retrieved later on with the following calls: \begin{lstlisting}{} Procedure gtk_clist_set_column_title(clist:PGtkCList; column:gint; title:Pgchar);cdecl; function gtk_clist_get_column_title(clist:PGtkCList; column:gint):Pgchar;cdecl; \end{lstlisting} Note that the column indices are 0 based. After the CList widget has been created, some properties can be set: \begin{lstlisting}{} gtk_clist_set_shadow_type(Result,GTK_SHADOW_ETCHED_OUT); \end{lstlisting} This call sets the border around the clist. The possible values for the last parameter (the \lstinline|TGtkShadowType|) of \lstinline|gtk_clist_set_shadow_type| are: \begin{description} \item[GTK\_SHADOW\_NONE] No border. \item[GTK\_SHADOW\_IN] the clist appears lowered. \item[GTK\_SHADOW\_OUT] the clist appears raised. \item[GTK\_SHADOW\_ETCHED\_IN] the clist appears with a lowered frame. \item[GTK\_SHADOW\_ETCHED\_OUT] the clist appears with a raised frame. \end{description} The justification of a column in the list can be set: \begin{lstlisting}{} gtk_clist_set_column_justification(result,2,GTK_JUSTIFY_RIGHT); \end{lstlisting} column 2 will contain the file sizes, so it is set right-justified. Other possible values are for justification are \lstinline|GTK_JUSTIFY_LEFT|, \lstinline|GTK_JUSTIFY_CENTER|, and \lstinline|GTK_JUSTIFY_FILL|, which have their obvious meanings. To be able to select multiple items (or rows) at once, the selection mode of the CList must be set: \begin{lstlisting}{} gtk_clist_set_selection_mode(Result,GTK_SELECTION_MULTIPLE); \end{lstlisting} Possible modes of selection are: \begin{description} \item[GTK\_SELECTION\_SINGLE] Only one row can be selected at any given time. \item[GTK\_SELECTION\_BROWSE] Multiple items can be selected, however the selection will always return 1 item. \item[GTK\_SELECTION\_MULTIPLE] Multiple items can be selected, and the selection will contain all selected items. \item[GTK\_SELECTION\_EXTENDED] The selection is always \lstinline|Nil|. \end{description} The selection is a field (\lstinline|selection|) of type \lstinline|PGList| in the \lstinline|TGtkCList| record. A \lstinline|PGlist| is a pointer to a doubly linked list with data pointers. More details about this will follow. The elements in the list list can be sorted. \begin{lstlisting}{} gtk_clist_set_auto_sort(Result,True); If DefCompare=Nil then DefCompare:=Result^.compare; gtk_clist_set_compare_func(Result, TGtkCListCompareFunc(@FileCompareFunc)); \end{lstlisting} By default, a CList sorts by comparing the texts in the current sort column of the items in the list. This sorting happens using the \lstinline|compare| function of the CList. The standard \lstinline|compare| function of the list is saved here in a variable \lstinline|DefCompare|, so it can still be used. Using the \lstinline|gtk_clist_set_compare_func| the compare function to be used when sorting can be set, and it is set to the function \lstinline|FileCompareFunc|, which will be discussed later on. The \lstinline|gtk_clist_set_auto_sort| can be used to set the auto-sort feature of the Clist. If auto-sort is on, adding new items to the CList will insert them in the correct order. If auto-sort is off, new items are appended to the beginning or end of the list. After the sort function is set, handlers are attached to 2 signals: \begin{lstlisting}{} gtk_signal_connect(PgtkObject(Result),'button_press_event', TGtkSignalFunc(@ShowPopup),MainWindow); gtk_signal_connect(PgtkObject(Result),'click_column', TGtkSignalFunc(@FileColumnClick),MainWindow); \end{lstlisting} The first handler connects to a mouse button press event. This will be used to detect a right mouse click, and to show a popup menu: \begin{lstlisting}{} Procedure ShowPopup(Widget : PGtkWidget; Event : PGdkEventButton; Window : PMainWindow);cdecl; begin if (event^.thetype=GDK_BUTTON_PRESS) and (event^.button=3) then gtk_menu_popup(Window^.PMFiles,Nil,Nil,Nil,NIl,3,event^.time); end; \end{lstlisting} The \lstinline|gtk_menu_popup| function does nothing but showing the menu; when a menu item is clicked, the menu will close by itself. The second handler connects to the 'click\_column' event. This event is emitted if the user clicks on the column header. It will be used to switch the sort order of the file list: \begin{lstlisting}{} Procedure FileColumnClick(List : PGtkCList;Column:gint; Window : PMainWindow);cdecl; Var I : longint; NS : TGtkSortType; begin If Column<>List^.sort_column Then begin gtk_clist_set_sort_type(List,GTK_SORT_ASCENDING); gtk_clist_set_sort_column(list,Column); end else begin If (List^.Sort_type=GTK_SORT_ASCENDING) Then NS:=GTK_SORT_DESCENDING else NS:=GTK_SORT_ASCENDING; gtk_clist_set_sort_type(List,NS); end; gtk_clist_sort(list); end; \end{lstlisting} The function starts by retrieving the current sort column. If it is different from the column the used clicked on, then 2 things are done: \begin{enumerate} \item The sort type is set to ascending. \item The sort column is set to the column the user clicked. \end{enumerate} If, on the other hand, the user clicks on a column that is the sort column, the sort type is simply reversed. After the sort column and sort type are set, the list is epxlicitly sorted. (neither of the calls that set the sort order or sort column forces a sort). The sort happens using the \lstinline|compare| function (\lstinline|FileCompareFunc|) that was set when the CList was created: \begin{lstlisting}{} Function FileCompareFunc(List:PGtkCList; Row1,Row2 : PGtkCListRow) : Longint; Cdecl; Var SC : Longint; begin SC:=List^.sort_column; If SC in [2,3] then begin SC:=SC-2; Result:=PLongint(Row1^.Data)[SC]-PLongint(Row2^.Data)[SC]; end Else Result:=DefCompare(List,Row1,Row2); end; \end{lstlisting} This function receives 3 arguments: \begin{itemize} \item The list that needs to be sorted. \item 2 pointers to the row objects that must be compared. \end{itemize} The result must be an integer that is negative if the first row should come before the second or larger than zero if the second row should come before the first. If the result is zero then the columns are considered the same. The function checks what the sort column is. If it is not the size (2) or date (3) column, then the default row compare function (which was saved in the \lstinline|DefCompare| variable when the list was created) is used to compare the rows. If the size or date columns must be compared, the user data associated with the rows is examined. As will be shown below, the user data will point to an array of 2 Longint values that describe the size and datestamp of the file. The approriate values are compared and the result is passed back. To fill the file list with data, the \lstinline|FillList| function is implemented: \begin{lstlisting}{} Function FillList(List : PGtkCList; Const Dir,Mask : String) : Integer; Var Info : TSearchRec; Size : Int64; I,J : longint; begin Result:=0; Size:=0; gtk_clist_freeze(List); Try gtk_clist_clear(List); If FindFirst (AddTrailingSeparator(Dir)+Mask, faAnyFile,Info)=0 then Repeat Inc(Size,Info.Size); AddFileToList(List,Info); Inc(Result); Until FindNext(Info)<>0; FindClose(info); finally For I:=0 to 4 do begin J:=gtk_clist_optimal_column_width(List,i); gtk_clist_set_column_width(List,i,J); end; gtk_clist_thaw(List) end; end; \end{lstlisting} This function is very straightforward. To start, it 'freezes' the list with \lstinline|gtk_clist_freeze|; this prevents the list from updating the screen each time a row is added or deleted. Omitting this call would cause serious performance degradation and screen flicker. After freezing the list, it is cleared; Then a simple loop is implemented that scans the given directory with the given file mask using the \lstinline|FindFirst|/\lstinline|FindNext| calls. For each file found it calls the \lstinline|AddFileToList| function, that will actually add the file to the list view, using the information found in the search record. The \lstinline|AddTrailingSeparator| adds a directory separator to a string containing the name of a directory if this does not end on a separator yet. It can be found in the \file{futils} unit. After the loop has finished, the optimal width for each column is retrieved using the \lstinline|gtk_clist_optimal_column_width| function and the result is used to set the column width. As a result, the columns will have the correct size for displaying all items. When this has been done, the list is 'thawed' with \lstinline|gtk_clist_thaw|, which means that it will repaint itself if needed. This happens in a \lstinline|finally| block since the \lstinline|gtk_clist_freeze| and \lstinline|gtk_clist_thaw| work with a reference counter. For each 'freeze' call the counter is increased. It is decreased with a 'thaw' call. When the counter reaches zero, the list is updated. The function that actually adds a row to the list view is quite simple: \begin{lstlisting}{} Procedure AddFileToList(List : PGtkCList; Info : TSearchRec); Var Texts : Array[1..6] of AnsiString; FSD : PLongint; I : longint; begin Texts[1]:=ExtractFileName(Info.Name); Texts[2]:=ExtractFileExt(Info.Name); Texts[3]:=FileSizeToString(Info.Size); Texts[4]:=DateTimeToStr(FileDateToDateTime(Info.Time)); Texts[5]:=FileAttrsToString(Info.Attr); Texts[6]:=''; i:=gtk_clist_append(List,@Texts[1]); FSD:=GetMem(2*SizeOf(Longint)); FSD[0]:=Info.Size; FSD[1]:=Info.Time; gtk_clist_set_row_data_full (List,I,FSD,@DestroySortData); end; \end{lstlisting} The \lstinline|gtk_clist_append| call accepts 2 paramers: a CList, and a pointer to an array of zero-terminated strings. The array must contain as much items as the CList has columns (in the above, the last column is always empty, as this gives a better visual effect). The call adds a column at the end of a list; An item can be inserted at the beginning of the list with \lstinline|gtk_clist_append|, which accepts the same parameters. An item can be inserted at certain position: \begin{lstlisting}{} gtk_clist_insert(clist:PGtkCList; row:gint; thetext:PPgchar);cdecl; \end{lstlisting} Note that all these calls do the same thing if the 'auto sort' was set for the CList. The \lstinline|FileAttrsToString| function converts file attributes to a string of characters that indicate whether a given attribute is present. It can be found in the \file{futils} unit and will not be shown here. After the file data was appended to the CList, an array of 2 longints is allocated on the heap. The first longint is filled with the size of the file, the second with the date of the file. The pointer to this array is then associated with the row that was just inserted with the \lstinline|gtk_clist_set_row_data_full| call. There are 2 calls to associate data with a row: \begin{lstlisting}{} gtk_clist_set_row_data(clist:PGtkCList; row:gint; data:gpointer);cdecl; gtk_clist_set_row_data_full(clist:PGtkCList; row:gint; data:gpointer; destroy: :TGtkDestroyNotify); \end{lstlisting} the first call is used to add data to a clist that will not need to be destroyed if the row is deleted. The second call can be used to pass a callback that will be called when the row is destroyed. In the case of the file list, the \lstinline|DestroySortData| call is used to dispose the array with sort data: \begin{lstlisting}{} Procedure DestroySortData(FSD : Pointer);cdecl; begin FreeMem(FSD); end; \end{lstlisting} The reason that the file list is cleared when the main window is destroyed now becomes apparent: when the list is cleared, all data associated with the file list is freed. If the call to \lstinline|gtk_clist_clear| is omitted before destroying the main window, the list is not cleared and all data stays in memory even after the window closes. The display of the column titles of the file list can be switched on or off. To do this a check menu item ('Hide titles') is added to the 'View' menu. If the menu is clicked, the following callback is executed: \begin{lstlisting}{} Procedure ToggleFileListTitles(Sender : PGtkCheckMenuItem; Window : PMainWindow);cdecl; begin If active(Sender^)=0 then gtk_clist_column_titles_show(Window^.FileList) else gtk_clist_column_titles_hide(Window^.FileList) end; \end{lstlisting} The \lstinline|active| function checks whether a check menu item is currently checked ot not and shows or hides the titles. Not only can the column titles be switched on or off, it is also possible to control whether or not a given column must be shown; Under the 'View' menu, there is a 'Hide columns' submenu that contains 4 check menus that can be used to toggle the visibility of the columns in the file list. All the check menu items are connected to the following callback: \begin{lstlisting}{} Procedure ToggleFileListColumns(Sender : PGtkCheckMenuItem; Window : PMainWindow);cdecl; Var Col : Longint; begin With Window^ do If Sender=MIShowExt Then Col:=1 else if Sender=MiShowSize Then Col:=2 else if Sender=MIShowDate then Col:=3 else Col:=4; gtk_clist_set_column_visibility(Window^.FileList, Col, (Active(Sender^)=0)); end; \end{lstlisting} The call gets as 'user data' a pointer to the main window record. Using this it checks which menu emitted the call, and updates the corresponding column with the \lstinline|gtk_clist_set_column_visibility| function. More attributes of a CList can be set, but they will not be discussed here; the GTK documentation and tutorial offer an overview of the possibilities. The selection mode of the CList has been set to allow selection of multiple rows. The Clist maintains a linked list (A Glist) with the rows that are part of the selection. The linked list contains the indexes of the selected rows in it's associated data. The linked list \lstinline|Glist| is often used in GTK applications. It consists of the following records: \begin{lstlisting}{} TGList = record data gpointer; next,prev : PGlist; end; PGlist=^TGlist; \end{lstlisting} The selection of a CList is of type \lstinline|PGlist|. The \lstinline|data| pointer can be typecasted to an integer to return the index of a selected row. The following function walks the selection linked list and stores the associated filenames in a \lstinline|TStrings| class: \begin{lstlisting}{} Procedure GetFileSelection (List : PGtkClist; Selection : TStrings); Var SList : PGList; Index : Longint; P : PChar; begin Selection.Clear; Slist:=List^.Selection; While SList<>nil do begin Index:=Longint(SList^.Data); gtk_clist_get_text(List,Index,0,@p); Selection.Add(StrPas(p)); SList:=g_list_next(SList); end; end; \end{lstlisting} The \lstinline|gtk_clist_get_text| retrieves the text of a given cell in the CList (a similar function exists to set the text) , and the \lstinline|g_list_next| jumps to the next element in the linked list. The \lstinline|TStrings| class is the standard string container as defined in the \lstinline|Classes| unit of Free Pascal (or Delphi). The above function will be used to retrieve the list of selected files so operations can be done on the selection. To retrieve the first (and possibly only) item of a selection, and the number of items in a selection, the following functions can be used: \begin{lstlisting}{} Function GetFileFirstSelection (List : PGtkClist) : String; Var SList : PGList; Index : Longint; P : PChar; begin Result:=''; Slist:=List^.Selection; If SList<>nil then begin Index:=Longint(SList^.Data); gtk_clist_get_text(List,Index,0,@p); Result:=StrPas(p); end; end; Function GetFileSelectionCount (List : PGtkClist) : Longint; Var SList : PGList; begin Slist:=List^.Selection; Result:=0; While SList<>nil do begin Inc(Result); SList:=g_list_next(SList); end; end; \end{lstlisting} These functions will be used further on. The filelist is now ready to be used. To be able to select a directory from which the files should be displayed, a Tree widget is used. How to create this tree and connect it to the file list is explained in the next section. \section{The directory tree} The directory tree will allow the user to browse through the directories on his system. When a directory is selected, the file view should be updated to show the files in the selected directory. To make the directory tree more efficient and less memory consuming, the tree is not filled with the whole directory tree at once. Instead, only 2 levels of directories will be put in the tree. The tree is progessively filled as the user expands the directory nodes. The directory tree is created in the following function: \begin{lstlisting}{} Function NewDirtree (MainWindow : PMainWindow) : PGtkTree; begin Result:=PGtkTree(gtk_tree_new()); With MainWindow^ do begin TreeScrollWindow:=PGtkScrolledWindow(gtk_scrolled_window_new(Nil,Nil)); gtk_widget_show(PGtkWidget(TreeScrollWindow)); gtk_scrolled_window_set_policy(TreeScrollWindow, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_scrolled_window_add_with_viewport(TreeScrollWindow,PGtkWidget(Result)); RootNode:=PGtkTreeItem(gtk_tree_Item_new_with_label(Pchar(PathSeparator))); gtk_tree_append(Result,PgtkWidget(RootNode)); scandirs(PathSeparator,Result, RootNode,True,MainWindow); gtk_tree_item_expand(rootnode); end; end; \end{lstlisting} The function starts off by creating the tree widget which is the return value of the function. Similar to the Clist, the tree widget does not possess functionality for displaying scroll bars, so a 'scrolled window' is created, in which the tree widget is placed. A tree can have one or more tree items connected to it. Each of these tree items can in turn have a tree associated with it, which in turn can again have tree items associated. This way the tree is recursively constructed. The directory tree is filled with 1 tree item, which will represent the root directory of the disk which is browsed with the file explorer; The \lstinline|gtk_tree_item_new_with_label| call returns a new tree item, which is then appended to the tree using the \lstinline|gtk_tree_append| call. After this is done, the directories below the root directory are scanned and appended to the root node in the \lstinline|scandirs| function, explained below. If the root node was filled, then it is expanded with \lstinline|gtk_tree_item_expand| (it can be collapsed with \lstinline|gtk_tree_item_collapse|) The \lstinline|scandirs| function scans a given directory for subdirectories and appends each directory to a subtree of a given node. The subtree is created if needed: \begin{lstlisting}{} Procedure Scandirs(Path: String; Tree : PgtkTree; Node: PGtkTreeItem ; SubSub : Boolean; Window : PMainWindow); Var NewTree : PGtkTree; NewNode : PGtkTreeItem; Info : TSearchRec; S,FP : AnsiString; begin NewTree:=Nil; FP:=AddTrailingSeparator(Path); If FindFirst(FP+'*.*',faAnyfile,Info)=0 then Try repeat If ((Info.Attr and faDirectory)=faDirectory) then begin S:=Info.Name; If (S<>'.') and (S<>'..') then begin If (Node<>Nil) then begin If (NewTree=Nil) and (node<>Nil) then begin NewTree:=PGtkTree(gtk_tree_new); gtk_tree_item_set_subtree(Node,PGtkWidget(NewTree)); end end else NewTree:=Tree; NewNode:=PGtkTreeItem(gtk_tree_item_new_with_label(Pchar(S))); gtk_tree_append(NewTree,PgtkWidget(NewNode)); gtk_signal_connect(PGtkObject(NewNode),'select', TGtkSignalFunc(@DirSelect),Window); gtk_signal_connect(PGtkObject(NewNode),'expand', TGtkSignalFunc(@DirExpand),Window); If SubSub then ScanDirs(FP+S,Tree,NewNode,False,Window); gtk_widget_show(PGtkWidget(NewNode)); end; end; until FindNext(Info)<>0; Finally FindClose(Info); end; gtk_widget_show(PGtkWidget(Node)); end; \end{lstlisting} The routine is a simple loop. If a subdirectory is found then a new tree widget is created (\lstinline|newTree|) and appended to the given node with the \lstinline|gtk_tree_item_set_subtree| call. For each found subdirectory a new treeitem is created and appended to the subtree. 2 signals handlers are connected to the created tree item, one for 'select' signal which is emitted when the user selects a tree item, and one for the 'expand' signal which is emitted when the user expands a node. Each of these handlers gets as data a pointer to the main window record. The \lstinline|SubSub| parameter is used to control the recursive behaviour. If it is set to \lstinline|True|, the \lstinline|Scandirs| function will call itself recursively, but only once. As a result only 2 levels of subdirectories are scanned. Finally, the created nodes are shown. When the user expands a node, the \lstinline|DirExpand| function is called: \begin{lstlisting}{} Procedure DirExpand(Item : PGtkTreeItem; Window : PMainWindow);cdecl; Var Dir : String; SubTree : PGtkTree; SubNodes : PGList; Node : PGtkTreeItem; begin SubTree:=PgtkTree(Item^.SubTree); SubNodes:=gtk_container_children(PGtkContainer(SubTree)); While SubNodes<>Nil do begin Node:=PgtkTreeItem(SubNodes^.Data); If (Node^.SubTree<>Nil) then gtk_tree_item_remove_subtree(Node); Scandirs(GetPathName(Node),Nil,Node,False,Window); SubNodes:=g_list_remove_link(SubNodes,SubNodes); end; end; \end{lstlisting} The function starts by retrieving the subtree of the tree item that triggered the callback. It then retrieves the list of subnodes (treeitems) of the subtree which represent the subdirectories of the directory node that is about to be expanded. The Tree object descends from the GTK container object, and keeps its treeitems in the container's children list. This list is a Glist. The \lstinline|gtk_container_children| returns a copy of the list containing the children. Then a simple loop is executed: for each of the found nodes, the subtree is destroyed if it exists: \lstinline|gtk_tree_item_remove_subtree| removes a subtree from a treeItem and destroys it. After the subtree is destroyed, at the subirectory is scanned for possible subdirecties (remark that the \lstinline|SubSub| parameter is set to \lstinline|false|) and the subtree is recreated if needed. The directory corresponding to a given node is calculated in the \lstinline|GetPathName| function, explained below. The next cycle of the loop is started by removing and destroying the first element of the GList with the \lstinline|g_list_remove_link| call: the call returns the new start of the list with the element removed. By passing the first element of the list as the element to be removed the whole list is traversed. When the user selects a tree item, the list view must be updated with the files in that directory. This is done in the \lstinline|DirSelect| handler for the 'select' signal: \begin{lstlisting}{} Procedure DirSelect(Item : PGtkTreeItem; Window : PMainWindow);cdecl; begin ShowDir(Window,GetPathName(Item)); end; Procedure ShowDir (Window : PMainWindow; Dir : String); begin With Window^ do begin FDir:=Dir; FillList(FileList,Dir,FMask); gtk_label_set_text(FilesHeader,pchar(Format(SFilesInDir,[Dir]))); end; end; \end{lstlisting} The \lstinline|Showdir| function will be called from other places as well hence it is put separately; The \lstinline|DirSelect| function does nothing but to call the ShowDir function after it has calculated the path of the treeitem that triggered the 'select' signal: \begin{lstlisting}{} Function GetPathName(Item : PGtkTreeItem) : String; Var P : PChar; PTree : PGtkTree; begin gtk_label_get(PgtkLabel(PGtkBin(Item)^.Child),@P); Result:=StrPas(P); If (PGtkWidget(item)^.Parent<>Nil) then begin PTree:=PGtkTree(PgtkWidget(Item)^.Parent); If (Ptree^.Level<>0) Then Result:=AddTrailingSeparator(GetPathName(PgtkTreeItem(PTree^.Tree_Owner)))+Result end; end; \end{lstlisting} It is a simple recursive mechanism. The only issue with this routine is that one should know that the parent of a tree item is a tree, and that the owner of the tree (in it's \lstinline|Tree_Owner| field) is in turn again a treeitem. The \lstinline|Level| field of a tree determines at what level the tree is located (i.e. the number of nodes present above the tree) and can be used to check when the algorithm should stop. An alternate approach would have been to associate with each node some user data, such as a string that is the full path name of the node. With this, the tree is created and is linked to the file list, so the user has the capability to select any directory and display it's contents; The user can also customize the view of the file list. However, no actions can be performed on the files. This is treated in the next sections, where a toolbar and popup menu are used to allow the user to do things with the shown files. \section{Adding a popup menu} To allow the user to do something with the displayed files, a popup menu is addd to the file list. Adding a popup menu is not different from adding a main menu to a form, just it will not be attached to a menu bar. The popup menu will be hidden till the user right-clicks in the file list. The popup menu is created in the following function: \begin{lstlisting}{} Function NewFilePopupMenu (MainWindow : PMainWindow) : PGtkMenu; begin result:=PGtkMenu(gtk_menu_new); gtk_signal_connect(PGtkObject(result),'show', TGtkSignalFunc(@PMFilesActivate),MainWindow); With MainWindow^ do begin PMIFileProperties:=AddItemToMenu(Result,Accel,'_Properties','', TgtkSignalFunc(@DoProperties), MainWindow); PMIFileDelete:=AddItemToMenu(Result,Accel,'_Delete','d', TgtkSignalFunc(@DeleteFile), MainWindow); end; end; \end{lstlisting} The \lstinline|AddItemToMenu| functions were developed in an earlier articles, and have been collected in the 'menus' unit. The 'show' handler attached to the menu is used to set the state of some of the menu items when the menu pops up: \begin{lstlisting}{} Procedure PMFilesActivate(Widget : PGtkWidget; Window : PMainWindow); cdecl; Var State : TGtkStateType; begin if GetFileSelectionCount(Window^.FileList)>1 then State:=GTK_STATE_INSENSITIVE else State:=GTK_STATE_Normal; gtk_widget_set_state(PgtkWidget(Window^.PMIFileProperties),State); end; \end{lstlisting} When more than 1 file is selected in the file view, the properties menu item is disabled. The popup menu will appear if the user clicks the right button in the file list; The necessary event handler for that (\lstinline|ShowPopup|) was attached to the CList and discussed earlier on. The delete menu item has the following 'click' handler: \begin{lstlisting}{} Procedure DeleteFile(Widget : PGtkWidget; Window : PMainWindow); cdecl; Var i : longint; S : TStringList; begin S:=TStringList.Create; Try GetFileSelection(Window^.FileList,S); For I:=0 to S.Count-1 do begin For I:=0 to S.Count-1 do SysUtils.DeleteFile(Window^.FDir+S[i]); end; Finally If S.Count>0 then RefreshFileView(Window); S.Free; end; end; \end{lstlisting} The routine simply retrieves the selection list and deletes all files present in it; After that the file view is refreshed. The properties popup menu action will be treated later on. \section{Adding a toolbar} The toolbar in the file explorer application will contain 2 buttons with a pixmap on them; the pixmap will be loaded from data compiled into the binary. The actions performed by the toolbar buttons will be the same as the actions in the popup menu: show a file's properties and delete the file. The creation of the toolbar for the file explorer program is done in the following function: \begin{lstlisting}{} Function NewToolbar (MainWindow : PMainWindow) : PGtkToolbar; begin Result:=pGtkToolBar(gtk_toolbar_new(GTK_ORIENTATION_HORIZONTAL, GTK_TOOLBAR_ICONS)); gtk_toolbar_append_item(result, Nil, 'File Properties', nil, CreateWidgetFromXPm(PgtkWidget(MainWindow^.Window), @PropertiesXPM), TgtkSignalFunc(@DoProperties), MainWindow); gtk_toolbar_append_item(result, Nil, 'Delete File', Nil, CreateWidgetFromXPm(PgtkWidget(MainWindow^.Window), @DeleteXPM), TgtkSignalFunc(@DeleteFile), MainWindow); end; \end{lstlisting} The \lstinline|gtk_toolbar_new| function creates a new toolbar. The first argument to this call specifies the orientation for the toolbar. Possible values for the orientation are: \begin{description} \item[GTK\_ORIENTATION\_HORIZONTAL] The toolbar is filled horizontally; \item[GTK\_ORIENTATION\_VERTICAL] The toolbar is filled vertically; \end{description} The second argument determines the style of the toolbar; it can have the following values: \begin{description} \item[GTK\_TOOLBAR\_TEXT] Toolbuttons just show a text. \item[GTK\_TOOLBAR\_ICONS] Toolbuttons just show a pixmap. \item[GTK\_TOOLBAR\_BOTH] toolbuttons show both a pixmap and text. \end{description} The style determines what widgets will be placed on new toolbuttons that are added with the \lstinline|gtk_toolbar_append_item| or \lstinline|gtk_toolbar_prepend_item| calls. If buttons are added to the toolbar manually, the style has no effect. The \lstinline|gtk_toolbar_append_item| call adds a new toolbar button to the end of a toolbar. The \lstinline|gtk_toolbar_prepend_item| item inserts a new button at the beginning of the toolbar. Both accept the following arguments: \begin{enumerate} \item a pointer to the toolbar to which the item should be added. \item a zero-terminated string with the text to be shown on the button. \item a zero-terminated string with the tooltip text (the hint) for the button. \item a zero terminated private tooltip text for the button. \item an icon wiget, usually a GtkPixmap. \item A callback function of type \lstinline|TGtkSignalFunc| that will be executed when the user clicks the button. \item Callback data pointer which will be passed to the callback. \end{enumerate} A toolbutton can also be inserted at a certain position with the \lstinline|gtk_toolbar_insert_item| call. It accepts an additional (last) argument, the position at which to insert the toolbutton. For the toolbar of the file explorer program, the buttons contain no text (since the \lstinline|GTK_TOOLBAR_ICONS| style was chosen for the toolbar) they do contain an icon, a pixmap widget. The pixmap widget is created with the following function: \begin{lstlisting}{} function CreateWidgetFromXPM (Window : PGtkWidget; Data : PPChar) : PGtkWidget; Var mask : PGdkBitmap; pixmap : PGdkPixMap; begin pixmap:=gdk_pixmap_create_from_xpm_d(window^.window,@mask,nil,ppgchar(Data)); Result:=gtk_pixmap_new(Pixmap,Mask); gtk_widget_show(Result); end; \end{lstlisting} This function accepts 2 arguments: A GTK window, and a pointer to an array or zero-terminated strings which describe the pixmap. With these it creates a gdk pixmap object with the \lstinline|gdk_pixmap_create_from_xpm_d| call. this function expects the following arguments: \begin{enumerate} \item A pointer to a GDK window object. In the above, the GDK window of the main window widget is used. This explains why the \lstinline|gtk_widget_realize| call was made when creating the main window: When the widget is realized, a window is allocated to it. If the main window widget was not realized, then it's gdk window would be nil. \item The address of a \lstinline|PGdkBitmap| which will be used to store the mask of the created pixmap. The mask determines the transparent items in the bitmap, and can be used when creating a pixmap widget. This may be nil. \item A pointer to a color that should be considered the transparent color. This may be nil, in which case a default color is used. \item A pointer to a XPM pixmap structure. \end{enumerate} After the GDK pixmap and the mask were created, a pixmap widget is created from the GDK bitmap, and the widget is shown. The pixmap data is in XPM format. The XPM format is an array of zero-terminated strings which are organized as follows: \begin{enumerate} \item A string describing the pixmap dimensions and the number of colors. The string is of the form \begin{verbatim} 'width height #colors chars/color', \end{verbatim} So the string \begin{verbatim} '16 16 4 1' \end{verbatim} means a 16x16 bitmap, using 4 colors, described by 1 character per color. \item A series of strings that describe the color. the number of strings should equal the count specified in the first string. The color descriptions should have the following form: \begin{verbatim} 'X c #YYYYYY' \end{verbatim} here 'X' must be replaced by N characters, where N is the number of characters per color that was specified in the first string. The YYYYYY is a RGB color value, in hex format. Each red,green or blue value must contain 2 or 4 characters. The string '\#FF0000' would describe red, just as '\#FFFF00000000' would describe red. Instead of a rgb value, 'None' can be specified to indicate a transparent color. Some examples of valid colors would be: \begin{verbatim} '. c #000000', { Black } '# c #000080', { Dark Blue } 'a c None', { Transparent } 'b c #f8fcf8', { greyish } \end{verbatim} \item A series of strings of characters, each string describes one line of the pixmap and is composed of the color characters described in the color section. Each line has the same length, namely the width of the image multiplied with the number of characters per color. Obviously, there should be as many strings as the height of the pixmap. \end{enumerate} The \file{fxbitmaps} unit contains 2 such bitmaps; comments have been added. After the toolbar has been added, the main form is finished. The form in action is shown in figure \ref{fig:mainwin}. \begin{figure}[ht] \caption{The main window in action.}\label{fig:mainwin} \epsfig{file=gtk4ex/mainwin.png,width=\textwidth} \end{figure} The toolbar contains a button to show the properties dialog. This dialog will show the various properties of a file, and is discussed in the next section. \section{Adding some dialogs} Adding some dialogs to the file explorer program is not so difficult. Three are created, an about dialog, a file properties dialog, and a dialog that allows to enter a file mask which will then be applied to the file view. All three dialogs will be based on the standard GTK dialog. Adding a dialog that shows the properties of a file is quite easy. The standard GTK dialog widget contains 3 widgets; a vertical box (\lstinline|vbox|) which can be used to drop widgets in, a separator and a horizontal box (\lstinline|action_area|), which can be used to put buttons (such as an 'OK' button) in. The file properties dialog consists mainly of a table packed with labels and some checkboxes. It is created in the following function: \begin{lstlisting}{} Type TFilePropertiesDialog = Record Window : PgtkDialog; Table : PGtkTable; OkButton : PGtkButton; Labels : Array[0..1,0..NrTableLines] of PGtkLabel; CheckBoxes : Array[CheckBoxLineStart..NrTableLines] of PgtkCheckButton; end; PFilePropertiesDialog = ^TFilePropertiesDialog; Function NewFilePropertiesDialog(FileName : String) : PFilePropertiesDialog; Const CheckAttrs : Array [CheckBoxLineStart..NrTableLines] of Integer = (faReadOnly,faArchive,faHidden,faSysFile); Var Info : TSearchRec; I : Longint; begin Result:=New(PFilePropertiesDialog); With Result^ do begin Window:=PgtkDialog(gtk_dialog_new); gtk_window_set_title(PgtkWindow(Window),SPropsTitle); gtk_window_set_modal(PgtkWindow(Window),True); gtk_window_set_policy(PgtkWindow(Window),0,0,0); gtk_window_set_position(PGtkWindow(Window),GTK_WIN_POS_CENTER); OkButton:=PGtkButton(gtk_button_new_with_label(SOK)); gtk_box_pack_start(PgtkBox(Window^.action_area),PGtkWidget(Okbutton),False,False,5); gtk_window_set_focus(PGtkWindow(Window),PGtkWidget(OkButton)); gtk_widget_show(PGtkWidget(OkButton)); \end{lstlisting} The above are standard things: The dialog window title is set, the dialog is made modal, the resizing of the window is prohibited with the \lstinline|gtk_window_set_policy| call. Then the window is told that it should position itself in the center of the screen with the \lstinline|gtk_window_set_position| call. The position specifier can be one of the following: \begin{description} \item[GTK\_WIN\_POS\_NONE] The window manager will decide where the window goes. \item[GTK\_WIN\_POS\_CENTER] The window is placed at the center of the screen. \item[GTK\_WIN\_POS\_MOUSE] The window is placed where the mouse cursor is. \end{description} After the window properties have been set, an OK button is placed in the action area, and it gets the focus. Next, a table is created with \lstinline|NrTableLines+1| rows and 2 columns, and put in the vbox area: \begin{lstlisting}{} Table:=PgtkTable(gtk_table_new(NrTableLines+1,2,TRUE)); gtk_box_pack_start(PGtkBox(Window^.vbox),PGtkWidget(Table),True,True,10); \end{lstlisting} Then the table is filled with labels that describe the various properties; the left column contains labels that simplu \begin{lstlisting}{} For I:=0 to NrTableLines do begin Labels[0,i]:=PGtkLabel(gtk_label_new(LabelTexts[i])); gtk_label_set_justify(Labels[0,I],GTK_JUSTIFY_RIGHT); gtk_table_attach_defaults(Table,PgtkWidget(Labels[0,I]),0,1,I,I+1); end; For I:=0 to CheckboxLineStart-1 do begin Labels[1,i]:=PGtkLabel(gtk_label_new('')); gtk_label_set_justify(Labels[1,I],GTK_JUSTIFY_LEFT); gtk_table_attach_defaults(Table,PgtkWidget(Labels[1,I]),1,2,I,I+1); end; \end{lstlisting} The file attributes will be represented with checkboxes: \begin{lstlisting}{} For I:=CheckboxLineStart to NrTableLines do begin checkBoxes[i]:=PgtkCheckButton(gtk_check_button_new_with_label(CheckBoxTexts[I])); gtk_widget_set_state(PGtKWidget(CheckBoxes[i]),GTK_STATE_INSENSITIVE); gtk_table_attach_defaults(Table,PgtkWidget(CheckBoxes[i]),1,2,I,I+1); end; \end{lstlisting} The checkboxes are made inactive, so the user cannot change them. After all labels and checkboxes are put in place, the file information is put into various places: \begin{lstlisting}{} gtk_label_set_text(Labels[1,0],PChar(ExtractFileName(FileName))); gtk_label_set_text(Labels[1,1],PChar(ExtractFilePath(FileName))); gtk_label_set_text(Labels[1,2],PChar(ExtractFileExt(FileName)+SFile)); If FindFirst(FileName,faAnyFile,Info)=0 Then begin gtk_label_set_text(Labels[1,3],PChar(FileSizeToString(Info.Size))); gtk_label_set_text(Labels[1,4],PChar(DateTimeToStr(FileDateToDateTime(Info.Time)))); For I:=CheckboxLineStart to NrTableLines do If (CheckAttrs[i] and Info.Attr)=CheckAttrs[i] then gtk_toggle_button_set_active(PgtkToggleButton(CheckBoxes[I]),True); FindClose(Info); end; \end{lstlisting} Finally, the 'destroy' callback for the window is set, and the OK button's 'click' signal is attached to the destroy method of the window widget: \begin{lstlisting}{} gtk_signal_connect(PGtkObject(Window),'destroy', TGTKSignalFunc(@DestroyPropDialog),Result); gtk_signal_connect_object(PgtkObject(OKButton),'clicked', GTK_SIGNAL_FUNC(@gtk_widget_destroy), PGTKOBJECT(Window)); end; end; \end{lstlisting} Showing the properties dialog is simple: \begin{lstlisting}{} Procedure ShowFilePropertiesDialog(Dialog : PFilePropertiesDialog); begin gtk_widget_show_all(PgtkWidget(Dialog^.Window)); end; \end{lstlisting} The result of all this is shown in figure \ref{fig:fileprops}. \begin{figure}[ht] \begin{center} \caption{The file properties dialog.}\label{fig:fileprops} \epsfig{file=gtk4ex/fileprops.png,width=8cm} \end{center} \end{figure} The handling of the mask form is a little bit more complicated than the properties dialog, since the mask form should return some information to the main form. The creation of the mask form is again a standard matter, and the reader is referred to the code on the CD-ROM to see how it is handled. The only thing worth noting is the handling of the click on the 'OK' button that appears on the form. \begin{lstlisting}{} gtk_signal_connect(PgtkObject(OKButton),'clicked', TGtkSignalFunc(@ApplyMask),Result); gtk_signal_connect_object(PgtkObject(OKButton),'clicked', GTK_SIGNAL_FUNC(@gtk_widget_destroy), PGTKOBJECT(Window)); \end{lstlisting} Two handlers are added to the 'clicked' signal of the 'OK' button. The first one is pointed to a function that will apply the mask, and the second one is redirected to the destroy method of the dialog window wigdet. \begin{lstlisting}{} Procedure ApplyMask(Widget : PGtkWidget; Window : PMaskForm);cdecl; begin With Window^ do begin Mask:=StrPas(gtk_entry_get_text(EMask)); If (CallBack<>Nil) then CallBack(Mask,CallBackData); end; end; \end{lstlisting} The \lstinline|TMaskForm| record that contains fields for all widgets on the mask entry form also contains 2 fields that allow the OK button to notify the calling program of the new mask: \begin{lstlisting}{} TMaskCallBack = Procedure (Mask : String; Data : Pointer); TMaskForm = Record { ... widget fields ... } Mask : ShortString; CallBack : TMaskCallBack; CallBackData : Pointer; end; PMaskForm = ^TMaskForm; \end{lstlisting} If the callback field is set, then the \lstinline|ApplyMask| function will call it and pass it the new mask and some arbitrary pointer. The main form contains a 'file mask' menu item, which has the following 'click' handler: \begin{lstlisting}{} procedure DoMask(Widget : PGtkWidget ; MainForm : PMainWindow ); cdecl; Var S : AnsiString; begin With NewMaskForm^ do begin S:=MainForm^.FMask; gtk_entry_set_text(EMask,PChar(S)); CallBack:=@ApplyMask; CallBackData:=MainForm; gtk_widget_show_all(PgtkWidget(Window)); end; end; \end{lstlisting} When the user clicks the 'file mask' menu item, A mask entry form is created. The current file mask is filled in the entry widget (\lstinline|EMask|). The callback is set, and the callbackdata is set to the pointer to the main window record. The callback that is executed when the user clicks the OK button on the mask form is the following: \begin{lstlisting}{} Procedure ApplyMask(Mask : String; Data : Pointer); begin PMainWindow(data)^.FMask:=Mask; RefreshFileView(PMainWindow(Data)); end; \end{lstlisting} The reason that this system of callbacks is needed is that the \lstinline|gtk_widget_show_all| immediatly returns when the mask entry form is shown. Even though the mask entry form dialog is a modal dialog (i.e. it alone will respond to mouse clicks and key presses) the call returns immediatly, there is no counterpart for the Delphi \lstinline|ShowModal| function. When the \lstinline|gtk_widget_show_all| returns, the mask entry form is still on the screen, so the changes made in the mask form must be communicated back to the main form by means of a callback which is executed when the mask entry form is closed. The mask form in action is shown in figure \ref{fig:filemask}. \begin{figure}[ht] \begin{center} \caption{The file properties dialog.}\label{fig:filemask} \epsfig{file=gtk4ex/filemask.png,width=8cm} \end{center} \end{figure} \section{Finishing the application} In several places in this article, a reference was made to the main menu. The main menu is created in the \lstinline|NewMainMenu| function; since menus were discussed extensively in the previous article on programming GTK, the code will not be presented here. The various calls developed in the previous article have been collected in the \file{menus} unit. One additional call was added which adds a check menuitem to a menu; the call is similar to the regular menu item calls, and will not be discussed here. The application is built in such a way that it can easily be extended. Only 2 file actions have been implemented, but many more can be made. Missing functionality includes: \begin{itemize} \item Renaming of files. The CList allows to put an arbitrary widget into a cell; this functionality could be used to allow the user to change the filename by simply editing it. \item Moving and copying of files, using drag and drop. \item Duplicating the main window, or spawning a new window. \item Opening a file in another application. \item Improve the look of the file properties form. \item On Windows, support for showing different drives should be added. \end{itemize} And without doubt, many more can be found. \end{document}