mirror of
				https://gitlab.com/freepascal.org/fpc/source.git
				synced 2025-10-31 07:31:49 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			1499 lines
		
	
	
		
			58 KiB
		
	
	
	
		
			TeX
		
	
	
	
	
	
			
		
		
	
	
			1499 lines
		
	
	
		
			58 KiB
		
	
	
	
		
			TeX
		
	
	
	
	
	
| \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','<ctrl>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} | 
