12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499 |
- \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}
|