gtk4.tex 58 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499
  1. \documentclass[10pt]{article}
  2. \usepackage{a4}
  3. \usepackage{epsfig}
  4. \usepackage{listings}
  5. \usepackage{tabularx}
  6. \lstset{language=Delphi}%
  7. \lstset{basicstyle=\sffamily\small}%
  8. \lstset{commentstyle=\itshape}%
  9. \lstset{keywordstyle=\bfseries}%
  10. \lstset{blankstring=true}%
  11. \newcommand{\file}[1]{\textsf{#1}}
  12. \usepackage[pdftex]{hyperref}
  13. \newif\ifpdf
  14. \ifx\pdfoutput\undefined
  15. \pdffalse
  16. \else
  17. \pdfoutput=1
  18. \pdftrue
  19. \fi
  20. \begin{document}
  21. \title{Programming GTK in Free Pascal: Making a real-world application.}
  22. \author{Florian Kl\"ampfl\\and\\Micha\"el Van Canneyt}
  23. \date{January 2001}
  24. \maketitle
  25. \section{Introduction}
  26. In the third article on programming the GTK toolkit, the use of several
  27. GTK widgets is demonstrated by building a real-world application.
  28. The main widgets to be shown are the Toolbar, CList and Tree widgets.
  29. Along the way, some other widgets such as a dialog will be shown as well.
  30. The program to show all this will be a small file explorer. It will not
  31. perform all functions that one would expect from a file explorer, but it
  32. is not meant to be, either. It just demonstrates how one could go about when
  33. making a file explorer.
  34. The File explorer will have 2 main components. One is a directory tree
  35. which can be used to select a directory. the other is a Clist, a component
  36. that presents a list of items in a table with headings. The Clist will be
  37. used to display the files in the directory selected in the directory tree.
  38. The functionality included will be limited to viewing the properties of
  39. a file, and deleting a file. The view can be customized, and sorting of
  40. columns by clicking the column header is possible.
  41. Each window developed in the article will be described in a record, i.e.
  42. all window elements will have a field in a record that points to the
  43. GTK widget used. Several forms will be developed, and each form will be
  44. put in a separate unit. Signal callbacks will in general receive a
  45. 'userdata' pointer that points to the window record. This approach mimics
  46. the object oriented approach of GTK, and is similar to the approach in
  47. Delphi, where instead of a object, a window class is used.
  48. \section{The main window}
  49. The main window will consist of a menu, a tool bar, a directory tree and
  50. the file list. The bottom of the screen will contain a statusbar. Between
  51. the directory tree and the file list is a splitter that can be used to
  52. resize the directory tree.
  53. Right-clicking on the file list will show a popup menu, from which file
  54. actions can be selected.
  55. All the widgets in the main window will be stored in a big record
  56. \lstinline|TMainWindow|:
  57. \begin{lstlisting}{}
  58. TMainWindow = Record
  59. FDir,
  60. FMask : String;
  61. Window : PGtkWindow;
  62. Menu : PGtkMenuBar;
  63. Toolbar : PGtkToolBar;
  64. DirTree : PGtkTree;
  65. FileList : PGtkClist;
  66. Pane : PGtkPaned;
  67. StatusBar : PGtkStatusBar;
  68. FilesHeader,DirHeader : PGtkLabel;
  69. // helper objects - Menu
  70. Accel : PGtkAccelGroup;
  71. MFile,
  72. MView,
  73. MColumns,
  74. MHelp,
  75. // Main menu items
  76. PMFiles : PGtkMenu;
  77. MIFile,
  78. MIFileProperties,
  79. MIFileDelete,
  80. MIExit,
  81. MiColumns,
  82. MIAbout,
  83. MIHelp : PGtkMenuItem;
  84. MIShowTitles,
  85. MIShowExt,
  86. MIShowSize,
  87. MiShowDate,
  88. MIShowAttrs : PGtkCheckMenuItem;
  89. // Files PopupMenu Items:
  90. PMIFileProperties,
  91. PMIFileDelete : PGtkMenuItem;
  92. // Packing boxes
  93. VBox,
  94. LeftBox,
  95. RightBox : PGtkBox;
  96. // Scroll boxes
  97. TreeScrollWindow,
  98. ListScrollWindow : PGtkScrolledWindow;
  99. // Tree root node.
  100. RootNode : PGtkTreeItem;
  101. end;
  102. PMainWindow = ^TMainWindow;
  103. \end{lstlisting}
  104. The record resembles a form class definition as used in \lstinline|Delphi|, it
  105. contains all possible widgets shown on the window.
  106. The most important ones are of course the \lstinline|DirTree| and \lstinline|FileList|
  107. fields, the \lstinline|Menu| which will refer to the main menu and the
  108. \lstinline|PMfiles| which will hold the popup menu. The Status bar is of course
  109. in the \lstinline|StatusBar| field, and the \lstinline|ToolBar| field will hold the main
  110. toolbar of the application.
  111. The \lstinline|FDir| field will be used to hold the currently shown
  112. directory and the \lstinline|FMask| field can be used to store a file mask that
  113. determines what files will be shown in the list.
  114. All these fields are filled in using the function \lstinline|NewMainForm| :
  115. \begin{lstlisting}{}
  116. Function NewMainForm : PMainWindow;
  117. \end{lstlisting}
  118. The function starts as follows :
  119. \begin{lstlisting}{}
  120. begin
  121. Result:=New(PMainWindow);
  122. With Result^ do
  123. begin
  124. FMask:='*.*';
  125. Window:=PgtkWindow(gtk_window_new(GTK_WINDOW_TOPLEVEL));
  126. gtk_window_set_title(Window,SFileExplorer);
  127. gtk_widget_set_usize(PgtkWidget(Window),640,480);
  128. gtk_signal_connect (PGTKOBJECT (window), 'destroy',
  129. GTK_SIGNAL_FUNC (@destroy), Result);
  130. gtk_widget_realize(PgtkWidget(window));
  131. \end{lstlisting}
  132. This is a more or less standard GTK setup for a window. Note that the
  133. pointer to the window record is passed to the 'destroy' signal handler
  134. for the window, and that the window widget is realized (so a actual
  135. window is created). The necessity for the 'realize' call is explained below.
  136. After the window is created, the main widgets on the form are created:
  137. \begin{lstlisting}{}
  138. Menu:=NewMainMenu(Result);
  139. ToolBar:=NewToolbar(Result);
  140. StatusBar:=PgtkStatusBar(gtk_statusbar_new);
  141. FileList:=NewFileList(Result);
  142. DirTree:=NewDirtree(Result);
  143. PMFiles:=NewFilePopupMenu(Result);
  144. \end{lstlisting}
  145. The functions used to create these widgets will be discussed further on.
  146. \begin{description}
  147. \item[Menu] The menu is created in the function \lstinline|NewMainMenu|
  148. \item[ToolBar] The toolbar is created in the \lstinline|NewToolbar| function.
  149. \item[FileList] The CList component which will show the file data. Created
  150. using \lstinline|NewFileList|.
  151. \item[DirTree] The directory tree showing the directory structure of the
  152. disk is created using \lstinline|NewDirtree|.
  153. \item[PMFiles] is the popup menu for the file list and is created in the
  154. \lstinline|NewFilePopupMenu| function.
  155. \end{description}
  156. Each function will set the fields which contain the helper widgets.
  157. After the main widgets have been created, it is time to put them on the
  158. form, and the rest of the \lstinline|NewMainForm| function is concerned
  159. mainly with placing the widgets in appropriate containers.
  160. A splitter widget in GTK is called a \lstinline|paned window|. It can be created
  161. using one of the following functions:
  162. \begin{lstlisting}{}
  163. function gtk_hpaned_new : PGtkWidget;
  164. function gtk_vpaned_new : PGtkWidget;
  165. \end{lstlisting}
  166. Since the directory tree and file explorer window will be located left to
  167. each other, a \lstinline|gtk_hpaned_new| call is needed for the file explorer.
  168. The \lstinline|paned window| has 2 halves, in each of which a widget can be
  169. placed. This is done using the following calls:
  170. \begin{lstlisting}{}
  171. procedure gtk_paned_add1(paned:PGtkPaned; child:PGtkWidget);cdecl;
  172. procedure gtk_paned_add2(paned:PGtkPaned; child:PGtkWidget);cdecl;
  173. \end{lstlisting}
  174. The first function adds a widget to the left pane, the second to the right
  175. pane (or the top and bottom panes if the splitter is vertical).
  176. With this knowledge, the Directory Tree and File List can be put on the
  177. form. In the case of the file explorer, 2 widgets will be packed in vertical
  178. boxes which are on their turn put the left and right panes of the splitter:
  179. \begin{lstlisting}{}
  180. Pane:=PgtkPaned(gtk_hpaned_new);
  181. DirHeader:=PgtkLabel(gtk_label_new(pchar(SDirTree)));
  182. LeftBox:=PGtkBox(gtk_vbox_new(false,0));
  183. gtk_box_pack_start(Leftbox,PGtkWidget(DirHeader),False,False,0);
  184. gtk_box_pack_start(Leftbox,PgtkWidget(TreeScrollWindow),true,True,0);
  185. gtk_paned_add1(pane,PGtkWidget(Leftbox));
  186. \end{lstlisting}
  187. The left-hand side vertical box (\lstinline|LeftBox|) contains a label
  188. (\lstinline|DirHeader|) which serves as a heading for the directory tree (\lstinline|DirTree|).
  189. It displays a static text (in the constant \lstinline|SDirTree|).
  190. The right pane can be filled in a similar way with the file list:
  191. \begin{lstlisting}{}
  192. FilesHeader:=PgtkLabel(gtk_label_new(pchar(SFilesInDir)));
  193. RightBox:=PGtkBox(gtk_vbox_new(false,0));
  194. gtk_box_pack_start(Rightbox,PGtkWidget(FilesHeader),False,False,0);
  195. gtk_box_pack_start(Rightbox,PGtkWidget(ListScrollWindow),true,True,0);
  196. gtk_paned_add2(pane,PGtkWidget(Rightbox));
  197. \end{lstlisting}
  198. The right-hand side vertical box contains a label \lstinline|FileHeader|
  199. which serves as a heading for the file list (\lstinline|FileList|).
  200. It will be used to display the current directory name
  201. (\lstinline|SFilesInDir| constant).
  202. After the directory tree and file view have been put in a paned window,
  203. all that is left to do is to stack the statusbar, paned window, toolbar
  204. and menu in a vertical box \lstinline|VBox| which covers the whole window:
  205. \begin{lstlisting}{}
  206. VBox:=PGtkBox(gtk_vbox_new(false,0));
  207. gtk_container_add(PGtkContainer(Window),PgtkWidget(VBox));
  208. gtk_box_pack_start(vbox,PGtkWidget(Menu),False,False,0);
  209. gtk_box_pack_start(vbox,PGtkWidget(ToolBar),False,False,0);
  210. gtk_box_pack_start(vbox,PGtkWidget(Pane),true,true,0);
  211. gtk_box_pack_start(vbox,PGtkWidget(StatusBar),false,false,0);
  212. gtk_widget_show_all(PGtkWidget(vbox));
  213. end;
  214. end;
  215. \end{lstlisting}
  216. The destroy signal of the window does nothing except destroying the
  217. main window record and telling GTK to exit the event loop:
  218. \begin{lstlisting}{}
  219. procedure destroy(widget : pGtkWidget ; Window : PMainWindow); cdecl;
  220. begin
  221. gtk_clist_clear(Window^.FileList);
  222. dispose(Window);
  223. gtk_main_quit();
  224. end;
  225. \end{lstlisting}
  226. The call to \lstinline|gtk_clist_clear| serves to clear the file list window.
  227. The necessity for this call will be explained below.
  228. \section{The file list}
  229. The file list is constructed using the GTK CList widget. This is a powerful
  230. widget that contains a lot of functionality, comparable to the
  231. \lstinline|TListView| component found in Delphi.
  232. A the file list widget is created using the following function:
  233. \begin{lstlisting}{}
  234. Function NewFileList(MainWindow : PMainWindow) : PGtkClist;
  235. Const
  236. Titles : Array[1..6] of pchar =
  237. ('Name','ext','Size','Date','Attributes','');
  238. begin
  239. MainWindow^.ListScrollWindow:=
  240. PGtkScrolledWindow(gtk_scrolled_window_new(Nil,Nil));
  241. gtk_scrolled_window_set_policy(MainWindow^.ListScrollWindow,
  242. GTK_POLICY_AUTOMATIC,
  243. GTK_POLICY_AUTOMATIC);
  244. Result:=PGtkClist(Gtk_Clist_New_with_titles(6,@Titles));
  245. gtk_Container_add(PGTKContainer(MainWindow^.ListScrollWindow),
  246. PGtkWidget(Result));
  247. \end{lstlisting}
  248. A Clist object is not capable of drawing scroll bars if it contains too many
  249. items for its size, so first a \lstinline|Scrolled Window| is created in which
  250. the Clist object is embedded. A scrolled window is a container widget which
  251. does nothing except providing scrollbars for the widget it contains.
  252. A scrolled window is created using the \lstinline|gtk_scrolled_window_new|
  253. function:
  254. \begin{lstlisting}{}
  255. function gtk_scrolled_window_new(hadjustment:PGtkAdjustment;
  256. vadjustment:PGtkAdjustment):PGtkWidget
  257. \end{lstlisting}
  258. The \lstinline|Adjustment| parameters can be used to pass scrollbar widgets
  259. that the scrolled window should use to do it's work.
  260. If none are passed, the scrolled window will create the needed scrollbars
  261. by itself.
  262. The visibility of the scrollbars can be controlled with the policy property
  263. of the scrolled window:
  264. \begin{lstlisting}{}
  265. gtk_scrolled_window_set_policy(scrolled_window:PGtkScrolledWindow;
  266. hscrollbar_policy:TGtkPolicyType;
  267. vscrollbar_policy:TGtkPolicyType)
  268. \end{lstlisting}
  269. The horizontal and vertical policies can be set to the following values:
  270. \begin{description}
  271. \item[GTK\_POLICY\_AUTOMATIC] Scrollbars are only visible if they are needed.
  272. \item[GTK\_POLICY\_ALWAYS] Scrollbars are always visible.
  273. \end{description}
  274. After the creation of the scrolled window, the file list is created and
  275. added to the scrolled window. A CList widget can be created using 2 calls;
  276. \begin{lstlisting}{}
  277. function gtk_clist_new (columns:gint):PGtkWidget;
  278. function gtk_clist_new_with_titles (columns:gint;
  279. titles:PPgchar):PGtkWidget;
  280. \end{lstlisting}
  281. In both cases, the number of columns in the list must be passed. If
  282. the column header titles are fixed and known, they can be passed in the
  283. \lstinline|gtk_clist_new_with_titles| call, but they can still be set and
  284. retrieved later on with the following calls:
  285. \begin{lstlisting}{}
  286. Procedure gtk_clist_set_column_title(clist:PGtkCList;
  287. column:gint;
  288. title:Pgchar);cdecl;
  289. function gtk_clist_get_column_title(clist:PGtkCList;
  290. column:gint):Pgchar;cdecl;
  291. \end{lstlisting}
  292. Note that the column indices are 0 based.
  293. After the CList widget has been created, some properties can be set:
  294. \begin{lstlisting}{}
  295. gtk_clist_set_shadow_type(Result,GTK_SHADOW_ETCHED_OUT);
  296. \end{lstlisting}
  297. This call sets the border around the clist. The possible values for
  298. the last parameter (the \lstinline|TGtkShadowType|) of
  299. \lstinline|gtk_clist_set_shadow_type| are:
  300. \begin{description}
  301. \item[GTK\_SHADOW\_NONE] No border.
  302. \item[GTK\_SHADOW\_IN] the clist appears lowered.
  303. \item[GTK\_SHADOW\_OUT] the clist appears raised.
  304. \item[GTK\_SHADOW\_ETCHED\_IN] the clist appears with a lowered frame.
  305. \item[GTK\_SHADOW\_ETCHED\_OUT] the clist appears with a raised frame.
  306. \end{description}
  307. The justification of a column in the list can be set:
  308. \begin{lstlisting}{}
  309. gtk_clist_set_column_justification(result,2,GTK_JUSTIFY_RIGHT);
  310. \end{lstlisting}
  311. column 2 will contain the file sizes, so it is set right-justified.
  312. Other possible values are for justification are
  313. \lstinline|GTK_JUSTIFY_LEFT|, \lstinline|GTK_JUSTIFY_CENTER|, and
  314. \lstinline|GTK_JUSTIFY_FILL|, which have their obvious meanings.
  315. To be able to select multiple items (or rows) at once, the selection mode of
  316. the CList must be set:
  317. \begin{lstlisting}{}
  318. gtk_clist_set_selection_mode(Result,GTK_SELECTION_MULTIPLE);
  319. \end{lstlisting}
  320. Possible modes of selection are:
  321. \begin{description}
  322. \item[GTK\_SELECTION\_SINGLE] Only one row can be selected at any given
  323. time.
  324. \item[GTK\_SELECTION\_BROWSE] Multiple items can be selected, however the
  325. selection will always return 1 item.
  326. \item[GTK\_SELECTION\_MULTIPLE] Multiple items can be selected, and the
  327. selection will contain all selected items.
  328. \item[GTK\_SELECTION\_EXTENDED] The selection is always \lstinline|Nil|.
  329. \end{description}
  330. The selection is a field (\lstinline|selection|) of type \lstinline|PGList| in the
  331. \lstinline|TGtkCList| record. A \lstinline|PGlist| is a pointer to a doubly linked
  332. list with data pointers. More details about this will follow.
  333. The elements in the list list can be sorted.
  334. \begin{lstlisting}{}
  335. gtk_clist_set_auto_sort(Result,True);
  336. If DefCompare=Nil then
  337. DefCompare:=Result^.compare;
  338. gtk_clist_set_compare_func(Result,
  339. TGtkCListCompareFunc(@FileCompareFunc));
  340. \end{lstlisting}
  341. By default, a CList sorts by comparing the texts in the current sort column
  342. of the items in the list. This sorting happens using the \lstinline|compare|
  343. function of the CList. The standard \lstinline|compare| function of the list
  344. is saved here in a variable \lstinline|DefCompare|, so it can still be used.
  345. Using the \lstinline|gtk_clist_set_compare_func| the compare function to be
  346. used when sorting can be set, and it is set to the function
  347. \lstinline|FileCompareFunc|, which will be discussed later on.
  348. The \lstinline|gtk_clist_set_auto_sort| can be used to set the auto-sort
  349. feature of the Clist. If auto-sort is on, adding new items to the CList will
  350. insert them in the correct order. If auto-sort is off, new items are
  351. appended to the beginning or end of the list.
  352. After the sort function is set, handlers are attached to 2 signals:
  353. \begin{lstlisting}{}
  354. gtk_signal_connect(PgtkObject(Result),'button_press_event',
  355. TGtkSignalFunc(@ShowPopup),MainWindow);
  356. gtk_signal_connect(PgtkObject(Result),'click_column',
  357. TGtkSignalFunc(@FileColumnClick),MainWindow);
  358. \end{lstlisting}
  359. The first handler connects to a mouse button press event. This will be used
  360. to detect a right mouse click, and to show a popup menu:
  361. \begin{lstlisting}{}
  362. Procedure ShowPopup(Widget : PGtkWidget;
  363. Event : PGdkEventButton;
  364. Window : PMainWindow);cdecl;
  365. begin
  366. if (event^.thetype=GDK_BUTTON_PRESS) and
  367. (event^.button=3) then
  368. gtk_menu_popup(Window^.PMFiles,Nil,Nil,Nil,NIl,3,event^.time);
  369. end;
  370. \end{lstlisting}
  371. The \lstinline|gtk_menu_popup| function does nothing but showing the menu;
  372. when a menu item is clicked, the menu will close by itself.
  373. The second handler connects to the 'click\_column' event. This event is
  374. emitted if the user clicks on the column header. It will be used to switch
  375. the sort order of the file list:
  376. \begin{lstlisting}{}
  377. Procedure FileColumnClick(List : PGtkCList;Column:gint; Window : PMainWindow);cdecl;
  378. Var
  379. I : longint;
  380. NS : TGtkSortType;
  381. begin
  382. If Column<>List^.sort_column Then
  383. begin
  384. gtk_clist_set_sort_type(List,GTK_SORT_ASCENDING);
  385. gtk_clist_set_sort_column(list,Column);
  386. end
  387. else
  388. begin
  389. If (List^.Sort_type=GTK_SORT_ASCENDING) Then
  390. NS:=GTK_SORT_DESCENDING
  391. else
  392. NS:=GTK_SORT_ASCENDING;
  393. gtk_clist_set_sort_type(List,NS);
  394. end;
  395. gtk_clist_sort(list);
  396. end;
  397. \end{lstlisting}
  398. The function starts by retrieving the current sort column. If it is
  399. different from the column the used clicked on, then 2 things are done:
  400. \begin{enumerate}
  401. \item The sort type is set to ascending.
  402. \item The sort column is set to the column the user clicked.
  403. \end{enumerate}
  404. If, on the other hand, the user clicks on a column that is the sort column,
  405. the sort type is simply reversed. After the sort column and sort type are
  406. set, the list is epxlicitly sorted. (neither of the calls that set the sort
  407. order or sort column forces a sort).
  408. The sort happens using the \lstinline|compare| function (\lstinline|FileCompareFunc|)
  409. that was set when the CList was created:
  410. \begin{lstlisting}{}
  411. Function FileCompareFunc(List:PGtkCList; Row1,Row2 : PGtkCListRow) : Longint; Cdecl;
  412. Var
  413. SC : Longint;
  414. begin
  415. SC:=List^.sort_column;
  416. If SC in [2,3] then
  417. begin
  418. SC:=SC-2;
  419. Result:=PLongint(Row1^.Data)[SC]-PLongint(Row2^.Data)[SC];
  420. end
  421. Else
  422. Result:=DefCompare(List,Row1,Row2);
  423. end;
  424. \end{lstlisting}
  425. This function receives 3 arguments:
  426. \begin{itemize}
  427. \item The list that needs to be sorted.
  428. \item 2 pointers to the row objects that must be compared.
  429. \end{itemize}
  430. The result must be an integer that is negative if the first row should come
  431. before the second or larger than zero if the second row should come before
  432. the first. If the result is zero then the columns are considered the same.
  433. The function checks what the sort column is. If it is not the size (2) or
  434. date (3) column, then the default row compare function (which was saved in
  435. the \lstinline|DefCompare| variable when the list was created) is used to
  436. compare the rows. If the size or date columns must be compared, the user
  437. data associated with the rows is examined. As will be shown below, the user
  438. data will point to an array of 2 Longint values that describe the size and
  439. datestamp of the file. The approriate values are compared and the result is
  440. passed back.
  441. To fill the file list with data, the \lstinline|FillList| function is
  442. implemented:
  443. \begin{lstlisting}{}
  444. Function FillList(List : PGtkCList;
  445. Const Dir,Mask : String) : Integer;
  446. Var
  447. Info : TSearchRec;
  448. Size : Int64;
  449. I,J : longint;
  450. begin
  451. Result:=0;
  452. Size:=0;
  453. gtk_clist_freeze(List);
  454. Try
  455. gtk_clist_clear(List);
  456. If FindFirst (AddTrailingSeparator(Dir)+Mask,
  457. faAnyFile,Info)=0 then
  458. Repeat
  459. Inc(Size,Info.Size);
  460. AddFileToList(List,Info);
  461. Inc(Result);
  462. Until FindNext(Info)<>0;
  463. FindClose(info);
  464. finally
  465. For I:=0 to 4 do
  466. begin
  467. J:=gtk_clist_optimal_column_width(List,i);
  468. gtk_clist_set_column_width(List,i,J);
  469. end;
  470. gtk_clist_thaw(List)
  471. end;
  472. end;
  473. \end{lstlisting}
  474. This function is very straightforward. To start, it 'freezes' the list with
  475. \lstinline|gtk_clist_freeze|; this prevents the list from updating the
  476. screen each time a row is added or deleted. Omitting this call would cause
  477. serious performance degradation and screen flicker.
  478. After freezing the list, it is cleared; Then a simple loop is implemented
  479. that scans the given directory with the given file mask using the
  480. \lstinline|FindFirst|/\lstinline|FindNext| calls. For each file found
  481. it calls the \lstinline|AddFileToList| function, that will actually add the
  482. file to the list view, using the information found in the search record.
  483. The \lstinline|AddTrailingSeparator| adds a directory separator to a
  484. string containing the name of a directory if this does not end on a
  485. separator yet. It can be found in the \file{futils} unit.
  486. After the loop has finished, the optimal width for each column is
  487. retrieved using the \lstinline|gtk_clist_optimal_column_width| function
  488. and the result is used to set the column width. As a result, the columns will
  489. have the correct size for displaying all items.
  490. When this has been done, the list is 'thawed' with \lstinline|gtk_clist_thaw|,
  491. which means that it will repaint itself if needed. This happens in a
  492. \lstinline|finally| block since the \lstinline|gtk_clist_freeze| and
  493. \lstinline|gtk_clist_thaw| work with a reference counter. For each 'freeze'
  494. call the counter is increased. It is decreased with a 'thaw' call. When the
  495. counter reaches zero, the list is updated.
  496. The function that actually adds a row to the list view is quite simple:
  497. \begin{lstlisting}{}
  498. Procedure AddFileToList(List : PGtkCList; Info : TSearchRec);
  499. Var
  500. Texts : Array[1..6] of AnsiString;
  501. FSD : PLongint;
  502. I : longint;
  503. begin
  504. Texts[1]:=ExtractFileName(Info.Name);
  505. Texts[2]:=ExtractFileExt(Info.Name);
  506. Texts[3]:=FileSizeToString(Info.Size);
  507. Texts[4]:=DateTimeToStr(FileDateToDateTime(Info.Time));
  508. Texts[5]:=FileAttrsToString(Info.Attr);
  509. Texts[6]:='';
  510. i:=gtk_clist_append(List,@Texts[1]);
  511. FSD:=GetMem(2*SizeOf(Longint));
  512. FSD[0]:=Info.Size;
  513. FSD[1]:=Info.Time;
  514. gtk_clist_set_row_data_full (List,I,FSD,@DestroySortData);
  515. end;
  516. \end{lstlisting}
  517. The \lstinline|gtk_clist_append| call accepts 2 paramers: a CList, and a
  518. pointer to an array of zero-terminated strings. The array must contain as
  519. much items as the CList has columns (in the above, the last column is
  520. always empty, as this gives a better visual effect). The call adds a column
  521. at the end of a list; An item can be inserted at the beginning of the list
  522. with \lstinline|gtk_clist_append|, which accepts the same parameters. An
  523. item can be inserted at certain position:
  524. \begin{lstlisting}{}
  525. gtk_clist_insert(clist:PGtkCList; row:gint; thetext:PPgchar);cdecl;
  526. \end{lstlisting}
  527. Note that all these calls do the same thing if the 'auto sort' was set for
  528. the CList.
  529. The \lstinline|FileAttrsToString| function converts file attributes to a
  530. string of characters that indicate whether a given attribute is present.
  531. It can be found in the \file{futils} unit and will not be shown here.
  532. After the file data was appended to the CList, an array of 2 longints is
  533. allocated on the heap. The first longint is filled with the size of the
  534. file, the second with the date of the file. The pointer to this array is
  535. then associated with the row that was just inserted with the
  536. \lstinline|gtk_clist_set_row_data_full| call. There are 2 calls to
  537. associate data with a row:
  538. \begin{lstlisting}{}
  539. gtk_clist_set_row_data(clist:PGtkCList;
  540. row:gint;
  541. data:gpointer);cdecl;
  542. gtk_clist_set_row_data_full(clist:PGtkCList;
  543. row:gint; data:gpointer;
  544. destroy: :TGtkDestroyNotify);
  545. \end{lstlisting}
  546. the first call is used to add data to a clist that will not need to be
  547. destroyed if the row is deleted. The second call can be used to pass a
  548. callback that will be called when the row is destroyed.
  549. In the case of the file list, the \lstinline|DestroySortData| call is
  550. used to dispose the array with sort data:
  551. \begin{lstlisting}{}
  552. Procedure DestroySortData(FSD : Pointer);cdecl;
  553. begin
  554. FreeMem(FSD);
  555. end;
  556. \end{lstlisting}
  557. The reason that the file list is cleared when the main window is destroyed
  558. now becomes apparent: when the list is cleared, all data associated with
  559. the file list is freed. If the call to \lstinline|gtk_clist_clear| is
  560. omitted before destroying the main window, the list is not cleared and all
  561. data stays in memory even after the window closes.
  562. The display of the column titles of the file list can be switched on or off.
  563. To do this a check menu item ('Hide titles') is added to the 'View' menu.
  564. If the menu is clicked, the following callback is executed:
  565. \begin{lstlisting}{}
  566. Procedure ToggleFileListTitles(Sender : PGtkCheckMenuItem;
  567. Window : PMainWindow);cdecl;
  568. begin
  569. If active(Sender^)=0 then
  570. gtk_clist_column_titles_show(Window^.FileList)
  571. else
  572. gtk_clist_column_titles_hide(Window^.FileList)
  573. end;
  574. \end{lstlisting}
  575. The \lstinline|active| function checks whether a check menu item is currently
  576. checked ot not and shows or hides the titles.
  577. Not only can the column titles be switched on or off, it is also possible to
  578. control whether or not a given column must be shown;
  579. Under the 'View' menu, there is a 'Hide columns' submenu that contains 4
  580. check menus that can be used to toggle the visibility of the columns in the
  581. file list. All the check menu items are connected to the following callback:
  582. \begin{lstlisting}{}
  583. Procedure ToggleFileListColumns(Sender : PGtkCheckMenuItem;
  584. Window : PMainWindow);cdecl;
  585. Var Col : Longint;
  586. begin
  587. With Window^ do
  588. If Sender=MIShowExt Then
  589. Col:=1
  590. else if Sender=MiShowSize Then
  591. Col:=2
  592. else if Sender=MIShowDate then
  593. Col:=3
  594. else
  595. Col:=4;
  596. gtk_clist_set_column_visibility(Window^.FileList,
  597. Col,
  598. (Active(Sender^)=0));
  599. end;
  600. \end{lstlisting}
  601. The call gets as 'user data' a pointer to the main window record. Using this
  602. it checks which menu emitted the call, and updates the corresponding column
  603. with the \lstinline|gtk_clist_set_column_visibility| function.
  604. More attributes of a CList can be set, but they will not be discussed here;
  605. the GTK documentation and tutorial offer an overview of the possibilities.
  606. The selection mode of the CList has been set to allow selection of multiple
  607. rows. The Clist maintains a linked list (A Glist) with the rows that are
  608. part of the selection. The linked list contains the indexes of the selected
  609. rows in it's associated data.
  610. The linked list \lstinline|Glist| is often used in GTK applications.
  611. It consists of the following records:
  612. \begin{lstlisting}{}
  613. TGList = record
  614. data gpointer;
  615. next,prev : PGlist;
  616. end;
  617. PGlist=^TGlist;
  618. \end{lstlisting}
  619. The selection of a CList is of type \lstinline|PGlist|. The \lstinline|data|
  620. pointer can be typecasted to an integer to return the index of a selected
  621. row.
  622. The following function walks the selection linked list and stores the
  623. associated filenames in a \lstinline|TStrings| class:
  624. \begin{lstlisting}{}
  625. Procedure GetFileSelection (List : PGtkClist; Selection : TStrings);
  626. Var
  627. SList : PGList;
  628. Index : Longint;
  629. P : PChar;
  630. begin
  631. Selection.Clear;
  632. Slist:=List^.Selection;
  633. While SList<>nil do
  634. begin
  635. Index:=Longint(SList^.Data);
  636. gtk_clist_get_text(List,Index,0,@p);
  637. Selection.Add(StrPas(p));
  638. SList:=g_list_next(SList);
  639. end;
  640. end;
  641. \end{lstlisting}
  642. The \lstinline|gtk_clist_get_text| retrieves the text of a given cell in the
  643. CList (a similar function exists to set the text) , and the
  644. \lstinline|g_list_next| jumps to the next element in the linked list.
  645. The \lstinline|TStrings| class is the standard string container as defined
  646. in the \lstinline|Classes| unit of Free Pascal (or Delphi).
  647. The above function will be used to retrieve the list of selected files so
  648. operations can be done on the selection.
  649. To retrieve the first (and possibly only) item of a selection, and the
  650. number of items in a selection, the following functions can be used:
  651. \begin{lstlisting}{}
  652. Function GetFileFirstSelection (List : PGtkClist) : String;
  653. Var
  654. SList : PGList;
  655. Index : Longint;
  656. P : PChar;
  657. begin
  658. Result:='';
  659. Slist:=List^.Selection;
  660. If SList<>nil then
  661. begin
  662. Index:=Longint(SList^.Data);
  663. gtk_clist_get_text(List,Index,0,@p);
  664. Result:=StrPas(p);
  665. end;
  666. end;
  667. Function GetFileSelectionCount (List : PGtkClist) : Longint;
  668. Var
  669. SList : PGList;
  670. begin
  671. Slist:=List^.Selection;
  672. Result:=0;
  673. While SList<>nil do
  674. begin
  675. Inc(Result);
  676. SList:=g_list_next(SList);
  677. end;
  678. end;
  679. \end{lstlisting}
  680. These functions will be used further on.
  681. The filelist is now ready to be used. To be able to select a directory from
  682. which the files should be displayed, a Tree widget is used. How to create
  683. this tree and connect it to the file list is explained in the next section.
  684. \section{The directory tree}
  685. The directory tree will allow the user to browse through the directories on
  686. his system. When a directory is selected, the file view should be updated
  687. to show the files in the selected directory.
  688. To make the directory tree more efficient and less memory consuming, the
  689. tree is not filled with the whole directory tree at once. Instead, only 2
  690. levels of directories will be put in the tree. The tree is progessively
  691. filled as the user expands the directory nodes.
  692. The directory tree is created in the following function:
  693. \begin{lstlisting}{}
  694. Function NewDirtree (MainWindow : PMainWindow) : PGtkTree;
  695. begin
  696. Result:=PGtkTree(gtk_tree_new());
  697. With MainWindow^ do
  698. begin
  699. TreeScrollWindow:=PGtkScrolledWindow(gtk_scrolled_window_new(Nil,Nil));
  700. gtk_widget_show(PGtkWidget(TreeScrollWindow));
  701. gtk_scrolled_window_set_policy(TreeScrollWindow,
  702. GTK_POLICY_AUTOMATIC,
  703. GTK_POLICY_AUTOMATIC);
  704. gtk_scrolled_window_add_with_viewport(TreeScrollWindow,PGtkWidget(Result));
  705. RootNode:=PGtkTreeItem(gtk_tree_Item_new_with_label(Pchar(PathSeparator)));
  706. gtk_tree_append(Result,PgtkWidget(RootNode));
  707. scandirs(PathSeparator,Result, RootNode,True,MainWindow);
  708. gtk_tree_item_expand(rootnode);
  709. end;
  710. end;
  711. \end{lstlisting}
  712. The function starts off by creating the tree widget which is the return
  713. value of the function.
  714. Similar to the Clist, the tree widget does not possess functionality
  715. for displaying scroll bars, so a 'scrolled window' is created,
  716. in which the tree widget is placed.
  717. A tree can have one or more tree items connected to it. Each of these tree
  718. items can in turn have a tree associated with it, which in turn can again
  719. have tree items associated. This way the tree is recursively constructed.
  720. The directory tree is filled with 1 tree item, which will represent the root
  721. directory of the disk which is browsed with the file explorer; The
  722. \lstinline|gtk_tree_item_new_with_label| call returns a new tree item,
  723. which is then appended to the tree using the \lstinline|gtk_tree_append|
  724. call.
  725. After this is done, the directories below the root directory are scanned and
  726. appended to the root node in the \lstinline|scandirs| function, explained
  727. below. If the root node was filled, then it is expanded with
  728. \lstinline|gtk_tree_item_expand| (it can be collapsed with
  729. \lstinline|gtk_tree_item_collapse|)
  730. The \lstinline|scandirs| function scans a given directory for subdirectories
  731. and appends each directory to a subtree of a given node. The subtree is
  732. created if needed:
  733. \begin{lstlisting}{}
  734. Procedure Scandirs(Path: String; Tree : PgtkTree;
  735. Node: PGtkTreeItem ; SubSub : Boolean;
  736. Window : PMainWindow);
  737. Var
  738. NewTree : PGtkTree;
  739. NewNode : PGtkTreeItem;
  740. Info : TSearchRec;
  741. S,FP : AnsiString;
  742. begin
  743. NewTree:=Nil;
  744. FP:=AddTrailingSeparator(Path);
  745. If FindFirst(FP+'*.*',faAnyfile,Info)=0 then
  746. Try
  747. repeat
  748. If ((Info.Attr and faDirectory)=faDirectory) then
  749. begin
  750. S:=Info.Name;
  751. If (S<>'.') and (S<>'..') then
  752. begin
  753. If (Node<>Nil) then
  754. begin
  755. If (NewTree=Nil) and (node<>Nil) then
  756. begin
  757. NewTree:=PGtkTree(gtk_tree_new);
  758. gtk_tree_item_set_subtree(Node,PGtkWidget(NewTree));
  759. end
  760. end
  761. else
  762. NewTree:=Tree;
  763. NewNode:=PGtkTreeItem(gtk_tree_item_new_with_label(Pchar(S)));
  764. gtk_tree_append(NewTree,PgtkWidget(NewNode));
  765. gtk_signal_connect(PGtkObject(NewNode),'select',
  766. TGtkSignalFunc(@DirSelect),Window);
  767. gtk_signal_connect(PGtkObject(NewNode),'expand',
  768. TGtkSignalFunc(@DirExpand),Window);
  769. If SubSub then
  770. ScanDirs(FP+S,Tree,NewNode,False,Window);
  771. gtk_widget_show(PGtkWidget(NewNode));
  772. end;
  773. end;
  774. until FindNext(Info)<>0;
  775. Finally
  776. FindClose(Info);
  777. end;
  778. gtk_widget_show(PGtkWidget(Node));
  779. end;
  780. \end{lstlisting}
  781. The routine is a simple loop. If a subdirectory is found then a new
  782. tree widget is created (\lstinline|newTree|) and appended to the
  783. given node with the \lstinline|gtk_tree_item_set_subtree| call.
  784. For each found subdirectory a new treeitem is created and appended to
  785. the subtree. 2 signals handlers are connected to the created tree item,
  786. one for 'select' signal which is emitted when the user selects a tree item,
  787. and one for the 'expand' signal which is emitted when the user expands a
  788. node. Each of these handlers gets as data a pointer to the main window
  789. record.
  790. The \lstinline|SubSub| parameter is used to control the recursive behaviour.
  791. If it is set to \lstinline|True|, the \lstinline|Scandirs| function will
  792. call itself recursively, but only once. As a result only 2 levels of
  793. subdirectories are scanned.
  794. Finally, the created nodes are shown.
  795. When the user expands a node, the \lstinline|DirExpand| function is
  796. called:
  797. \begin{lstlisting}{}
  798. Procedure DirExpand(Item : PGtkTreeItem; Window : PMainWindow);cdecl;
  799. Var
  800. Dir : String;
  801. SubTree : PGtkTree;
  802. SubNodes : PGList;
  803. Node : PGtkTreeItem;
  804. begin
  805. SubTree:=PgtkTree(Item^.SubTree);
  806. SubNodes:=gtk_container_children(PGtkContainer(SubTree));
  807. While SubNodes<>Nil do
  808. begin
  809. Node:=PgtkTreeItem(SubNodes^.Data);
  810. If (Node^.SubTree<>Nil) then
  811. gtk_tree_item_remove_subtree(Node);
  812. Scandirs(GetPathName(Node),Nil,Node,False,Window);
  813. SubNodes:=g_list_remove_link(SubNodes,SubNodes);
  814. end;
  815. end;
  816. \end{lstlisting}
  817. The function starts by retrieving the subtree of the tree item that
  818. triggered the callback. It then retrieves the list of subnodes (treeitems)
  819. of the subtree which represent the subdirectories of the directory node
  820. that is about to be expanded. The Tree object descends from the GTK
  821. container object, and keeps its treeitems in the container's children
  822. list. This list is a Glist. The \lstinline|gtk_container_children| returns
  823. a copy of the list containing the children.
  824. Then a simple loop is executed: for each of
  825. the found nodes, the subtree is destroyed if it exists:
  826. \lstinline|gtk_tree_item_remove_subtree| removes a subtree from a treeItem
  827. and destroys it.
  828. After the subtree is destroyed, at the subirectory is scanned for possible
  829. subdirecties (remark that the \lstinline|SubSub| parameter is set to
  830. \lstinline|false|) and the subtree is recreated if needed.
  831. The directory corresponding to a given node is calculated in the
  832. \lstinline|GetPathName| function, explained below.
  833. The next cycle of the loop is started by removing and destroying the first
  834. element of the GList with the \lstinline|g_list_remove_link| call:
  835. the call returns the new start of the list with the element removed. By
  836. passing the first element of the list as the element to be removed the
  837. whole list is traversed.
  838. When the user selects a tree item, the list view must be updated with
  839. the files in that directory. This is done in the \lstinline|DirSelect|
  840. handler for the 'select' signal:
  841. \begin{lstlisting}{}
  842. Procedure DirSelect(Item : PGtkTreeItem; Window : PMainWindow);cdecl;
  843. begin
  844. ShowDir(Window,GetPathName(Item));
  845. end;
  846. Procedure ShowDir (Window : PMainWindow; Dir : String);
  847. begin
  848. With Window^ do
  849. begin
  850. FDir:=Dir;
  851. FillList(FileList,Dir,FMask);
  852. gtk_label_set_text(FilesHeader,pchar(Format(SFilesInDir,[Dir])));
  853. end;
  854. end;
  855. \end{lstlisting}
  856. The \lstinline|Showdir| function will be called from other places as
  857. well hence it is put separately; The \lstinline|DirSelect| function
  858. does nothing but to call the ShowDir function after it has calculated the
  859. path of the treeitem that triggered the 'select' signal:
  860. \begin{lstlisting}{}
  861. Function GetPathName(Item : PGtkTreeItem) : String;
  862. Var P : PChar;
  863. PTree : PGtkTree;
  864. begin
  865. gtk_label_get(PgtkLabel(PGtkBin(Item)^.Child),@P);
  866. Result:=StrPas(P);
  867. If (PGtkWidget(item)^.Parent<>Nil) then
  868. begin
  869. PTree:=PGtkTree(PgtkWidget(Item)^.Parent);
  870. If (Ptree^.Level<>0) Then
  871. Result:=AddTrailingSeparator(GetPathName(PgtkTreeItem(PTree^.Tree_Owner)))+Result
  872. end;
  873. end;
  874. \end{lstlisting}
  875. It is a simple recursive mechanism. The only issue with this
  876. routine is that one should know that the parent of a tree item is a tree,
  877. and that the owner of the tree (in it's \lstinline|Tree_Owner| field) is
  878. in turn again a treeitem. The \lstinline|Level| field of a tree determines
  879. at what level the tree is located (i.e. the number of nodes present above
  880. the tree) and can be used to check when the algorithm should stop.
  881. An alternate approach would have been to associate with each node some
  882. user data, such as a string that is the full path name of the node.
  883. With this, the tree is created and is linked to the file list, so the
  884. user has the capability to select any directory and display it's contents;
  885. The user can also customize the view of the file list.
  886. However, no actions can be performed on the files. This is treated in the
  887. next sections, where a toolbar and popup menu are used to allow the user to
  888. do things with the shown files.
  889. \section{Adding a popup menu}
  890. To allow the user to do something with the displayed files, a popup menu is
  891. addd to the file list. Adding a popup menu is not different from adding a
  892. main menu to a form, just it will not be attached to a menu bar. The popup
  893. menu will be hidden till the user right-clicks in the file list.
  894. The popup menu is created in the following function:
  895. \begin{lstlisting}{}
  896. Function NewFilePopupMenu (MainWindow : PMainWindow) : PGtkMenu;
  897. begin
  898. result:=PGtkMenu(gtk_menu_new);
  899. gtk_signal_connect(PGtkObject(result),'show',
  900. TGtkSignalFunc(@PMFilesActivate),MainWindow);
  901. With MainWindow^ do
  902. begin
  903. PMIFileProperties:=AddItemToMenu(Result,Accel,'_Properties','',
  904. TgtkSignalFunc(@DoProperties),
  905. MainWindow);
  906. PMIFileDelete:=AddItemToMenu(Result,Accel,'_Delete','<ctrl>d',
  907. TgtkSignalFunc(@DeleteFile),
  908. MainWindow);
  909. end;
  910. end;
  911. \end{lstlisting}
  912. The \lstinline|AddItemToMenu| functions were developed in an earlier
  913. articles, and have been collected in the 'menus' unit.
  914. The 'show' handler attached to the menu is used to set the state
  915. of some of the menu items when the menu pops up:
  916. \begin{lstlisting}{}
  917. Procedure PMFilesActivate(Widget : PGtkWidget; Window : PMainWindow); cdecl;
  918. Var State : TGtkStateType;
  919. begin
  920. if GetFileSelectionCount(Window^.FileList)>1 then
  921. State:=GTK_STATE_INSENSITIVE
  922. else
  923. State:=GTK_STATE_Normal;
  924. gtk_widget_set_state(PgtkWidget(Window^.PMIFileProperties),State);
  925. end;
  926. \end{lstlisting}
  927. When more than 1 file is selected in the file view, the properties menu item
  928. is disabled.
  929. The popup menu will appear if the user clicks the right button in the file
  930. list; The necessary event handler for that (\lstinline|ShowPopup|) was
  931. attached to the CList and discussed earlier on.
  932. The delete menu item has the following 'click' handler:
  933. \begin{lstlisting}{}
  934. Procedure DeleteFile(Widget : PGtkWidget; Window : PMainWindow); cdecl;
  935. Var i : longint;
  936. S : TStringList;
  937. begin
  938. S:=TStringList.Create;
  939. Try
  940. GetFileSelection(Window^.FileList,S);
  941. For I:=0 to S.Count-1 do
  942. begin
  943. For I:=0 to S.Count-1 do
  944. SysUtils.DeleteFile(Window^.FDir+S[i]);
  945. end;
  946. Finally
  947. If S.Count>0 then
  948. RefreshFileView(Window);
  949. S.Free;
  950. end;
  951. end;
  952. \end{lstlisting}
  953. The routine simply retrieves the selection list and deletes all files
  954. present in it; After that the file view is refreshed.
  955. The properties popup menu action will be treated later on.
  956. \section{Adding a toolbar}
  957. The toolbar in the file explorer application will contain 2 buttons with
  958. a pixmap on them; the pixmap will be loaded from data compiled into the
  959. binary. The actions performed by the toolbar buttons will be the same as
  960. the actions in the popup menu: show a file's properties and delete the file.
  961. The creation of the toolbar for the file explorer program is done in the
  962. following function:
  963. \begin{lstlisting}{}
  964. Function NewToolbar (MainWindow : PMainWindow) : PGtkToolbar;
  965. begin
  966. Result:=pGtkToolBar(gtk_toolbar_new(GTK_ORIENTATION_HORIZONTAL,
  967. GTK_TOOLBAR_ICONS));
  968. gtk_toolbar_append_item(result,
  969. Nil,
  970. 'File Properties',
  971. nil,
  972. CreateWidgetFromXPm(PgtkWidget(MainWindow^.Window),
  973. @PropertiesXPM),
  974. TgtkSignalFunc(@DoProperties),
  975. MainWindow);
  976. gtk_toolbar_append_item(result,
  977. Nil,
  978. 'Delete File',
  979. Nil,
  980. CreateWidgetFromXPm(PgtkWidget(MainWindow^.Window),
  981. @DeleteXPM),
  982. TgtkSignalFunc(@DeleteFile),
  983. MainWindow);
  984. end;
  985. \end{lstlisting}
  986. The \lstinline|gtk_toolbar_new| function creates a new toolbar. The first
  987. argument to this call specifies the orientation for the toolbar. Possible
  988. values for the orientation are:
  989. \begin{description}
  990. \item[GTK\_ORIENTATION\_HORIZONTAL] The toolbar is filled horizontally;
  991. \item[GTK\_ORIENTATION\_VERTICAL] The toolbar is filled vertically;
  992. \end{description}
  993. The second argument determines the style of the toolbar; it can have the
  994. following values:
  995. \begin{description}
  996. \item[GTK\_TOOLBAR\_TEXT] Toolbuttons just show a text.
  997. \item[GTK\_TOOLBAR\_ICONS] Toolbuttons just show a pixmap.
  998. \item[GTK\_TOOLBAR\_BOTH] toolbuttons show both a pixmap and text.
  999. \end{description}
  1000. The style determines what widgets will be placed on new toolbuttons that
  1001. are added with the \lstinline|gtk_toolbar_append_item| or
  1002. \lstinline|gtk_toolbar_prepend_item| calls. If buttons are added to the
  1003. toolbar manually, the style has no effect.
  1004. The \lstinline|gtk_toolbar_append_item| call adds a new toolbar button
  1005. to the end of a toolbar. The \lstinline|gtk_toolbar_prepend_item| item
  1006. inserts a new button at the beginning of the toolbar. Both accept the
  1007. following arguments:
  1008. \begin{enumerate}
  1009. \item a pointer to the toolbar to which the item should be added.
  1010. \item a zero-terminated string with the text to be shown on the button.
  1011. \item a zero-terminated string with the tooltip text (the hint) for the button.
  1012. \item a zero terminated private tooltip text for the button.
  1013. \item an icon wiget, usually a GtkPixmap.
  1014. \item A callback function of type \lstinline|TGtkSignalFunc| that will be
  1015. executed when the user clicks the button.
  1016. \item Callback data pointer which will be passed to the callback.
  1017. \end{enumerate}
  1018. A toolbutton can also be inserted at a certain position with the
  1019. \lstinline|gtk_toolbar_insert_item| call. It accepts an additional (last)
  1020. argument, the position at which to insert the toolbutton.
  1021. For the toolbar of the file explorer program, the buttons contain no text
  1022. (since the \lstinline|GTK_TOOLBAR_ICONS| style was chosen for the toolbar)
  1023. they do contain an icon, a pixmap widget.
  1024. The pixmap widget is created with the following function:
  1025. \begin{lstlisting}{}
  1026. function CreateWidgetFromXPM (Window : PGtkWidget;
  1027. Data : PPChar) : PGtkWidget;
  1028. Var
  1029. mask : PGdkBitmap;
  1030. pixmap : PGdkPixMap;
  1031. begin
  1032. pixmap:=gdk_pixmap_create_from_xpm_d(window^.window,@mask,nil,ppgchar(Data));
  1033. Result:=gtk_pixmap_new(Pixmap,Mask);
  1034. gtk_widget_show(Result);
  1035. end;
  1036. \end{lstlisting}
  1037. This function accepts 2 arguments: A GTK window, and a pointer to an array
  1038. or zero-terminated strings which describe the pixmap. With these it creates
  1039. a gdk pixmap object with the \lstinline|gdk_pixmap_create_from_xpm_d| call.
  1040. this function expects the following arguments:
  1041. \begin{enumerate}
  1042. \item A pointer to a GDK window object. In the above, the GDK window of the
  1043. main window widget is used. This explains why the \lstinline|gtk_widget_realize|
  1044. call was made when creating the main window: When the widget is realized, a
  1045. window is allocated to it. If the main window widget was not realized, then
  1046. it's gdk window would be nil.
  1047. \item The address of a \lstinline|PGdkBitmap| which will be used to store
  1048. the mask of the created pixmap. The mask determines the transparent items
  1049. in the bitmap, and can be used when creating a pixmap widget. This may be
  1050. nil.
  1051. \item A pointer to a color that should be considered the transparent
  1052. color. This may be nil, in which case a default color is used.
  1053. \item A pointer to a XPM pixmap structure.
  1054. \end{enumerate}
  1055. After the GDK pixmap and the mask were created, a pixmap widget is created
  1056. from the GDK bitmap, and the widget is shown.
  1057. The pixmap data is in XPM format. The XPM format is an array of
  1058. zero-terminated strings which are organized as follows:
  1059. \begin{enumerate}
  1060. \item A string describing the pixmap dimensions and the number of colors.
  1061. The string is of the form
  1062. \begin{verbatim}
  1063. 'width height #colors chars/color',
  1064. \end{verbatim}
  1065. So the string
  1066. \begin{verbatim}
  1067. '16 16 4 1'
  1068. \end{verbatim}
  1069. means a 16x16 bitmap, using 4 colors, described by 1 character per color.
  1070. \item A series of strings that describe the color. the number of strings
  1071. should equal the count specified in the first string. The color descriptions
  1072. should have the following form:
  1073. \begin{verbatim}
  1074. 'X c #YYYYYY'
  1075. \end{verbatim}
  1076. here 'X' must be replaced by N characters, where N is the number of
  1077. characters per color that was specified in the first string. The YYYYYY
  1078. is a RGB color value, in hex format. Each red,green or blue value must
  1079. contain 2 or 4 characters. The string '\#FF0000' would describe red, just as
  1080. '\#FFFF00000000' would describe red.
  1081. Instead of a rgb value, 'None' can be specified to indicate a transparent
  1082. color.
  1083. Some examples of valid colors would be:
  1084. \begin{verbatim}
  1085. '. c #000000', { Black }
  1086. '# c #000080', { Dark Blue }
  1087. 'a c None', { Transparent }
  1088. 'b c #f8fcf8', { greyish }
  1089. \end{verbatim}
  1090. \item A series of strings of characters, each string describes one line of
  1091. the pixmap and is composed of the color characters described in the color
  1092. section. Each line has the same length, namely the width of the image
  1093. multiplied with the number of characters per color. Obviously, there
  1094. should be as many strings as the height of the pixmap.
  1095. \end{enumerate}
  1096. The \file{fxbitmaps} unit contains 2 such bitmaps; comments have been added.
  1097. After the toolbar has been added, the main form is finished. The
  1098. form in action is shown in figure \ref{fig:mainwin}.
  1099. \begin{figure}[ht]
  1100. \caption{The main window in action.}\label{fig:mainwin}
  1101. \epsfig{file=gtk4ex/mainwin.png,width=\textwidth}
  1102. \end{figure}
  1103. The toolbar contains a button to show the properties dialog. This dialog
  1104. will show the various properties of a file, and is discussed in the next
  1105. section.
  1106. \section{Adding some dialogs}
  1107. Adding some dialogs to the file explorer program is not so difficult.
  1108. Three are created, an about dialog, a file properties dialog, and a dialog
  1109. that allows to enter a file mask which will then be applied to the file
  1110. view. All three dialogs will be based on the standard GTK dialog.
  1111. Adding a dialog that shows the properties of a file is quite easy.
  1112. The standard GTK dialog widget contains 3 widgets; a vertical box
  1113. (\lstinline|vbox|) which can be used to drop widgets in, a separator
  1114. and a horizontal box (\lstinline|action_area|), which can be used to
  1115. put buttons (such as an 'OK' button) in.
  1116. The file properties dialog consists mainly of a table packed with labels and
  1117. some checkboxes. It is created in the following function:
  1118. \begin{lstlisting}{}
  1119. Type
  1120. TFilePropertiesDialog = Record
  1121. Window : PgtkDialog;
  1122. Table : PGtkTable;
  1123. OkButton : PGtkButton;
  1124. Labels : Array[0..1,0..NrTableLines] of PGtkLabel;
  1125. CheckBoxes : Array[CheckBoxLineStart..NrTableLines] of PgtkCheckButton;
  1126. end;
  1127. PFilePropertiesDialog = ^TFilePropertiesDialog;
  1128. Function NewFilePropertiesDialog(FileName : String) : PFilePropertiesDialog;
  1129. Const
  1130. CheckAttrs : Array [CheckBoxLineStart..NrTableLines] of Integer
  1131. = (faReadOnly,faArchive,faHidden,faSysFile);
  1132. Var
  1133. Info : TSearchRec;
  1134. I : Longint;
  1135. begin
  1136. Result:=New(PFilePropertiesDialog);
  1137. With Result^ do
  1138. begin
  1139. Window:=PgtkDialog(gtk_dialog_new);
  1140. gtk_window_set_title(PgtkWindow(Window),SPropsTitle);
  1141. gtk_window_set_modal(PgtkWindow(Window),True);
  1142. gtk_window_set_policy(PgtkWindow(Window),0,0,0);
  1143. gtk_window_set_position(PGtkWindow(Window),GTK_WIN_POS_CENTER);
  1144. OkButton:=PGtkButton(gtk_button_new_with_label(SOK));
  1145. gtk_box_pack_start(PgtkBox(Window^.action_area),PGtkWidget(Okbutton),False,False,5);
  1146. gtk_window_set_focus(PGtkWindow(Window),PGtkWidget(OkButton));
  1147. gtk_widget_show(PGtkWidget(OkButton));
  1148. \end{lstlisting}
  1149. The above are standard things: The dialog window title is set, the dialog is
  1150. made modal, the resizing of the window is prohibited with the
  1151. \lstinline|gtk_window_set_policy| call. Then the window is told that it
  1152. should position itself in the center of the screen with the
  1153. \lstinline|gtk_window_set_position| call. The position specifier can be one
  1154. of the following:
  1155. \begin{description}
  1156. \item[GTK\_WIN\_POS\_NONE] The window manager will decide where the window
  1157. goes.
  1158. \item[GTK\_WIN\_POS\_CENTER] The window is placed at the center of the
  1159. screen.
  1160. \item[GTK\_WIN\_POS\_MOUSE] The window is placed where the mouse cursor is.
  1161. \end{description}
  1162. After the window properties have been set, an OK button is placed in the
  1163. action area, and it gets the focus.
  1164. Next, a table is created with \lstinline|NrTableLines+1| rows and 2 columns,
  1165. and put in the vbox area:
  1166. \begin{lstlisting}{}
  1167. Table:=PgtkTable(gtk_table_new(NrTableLines+1,2,TRUE));
  1168. gtk_box_pack_start(PGtkBox(Window^.vbox),PGtkWidget(Table),True,True,10);
  1169. \end{lstlisting}
  1170. Then the table is filled with labels that describe the various properties;
  1171. the left column contains labels that simplu
  1172. \begin{lstlisting}{}
  1173. For I:=0 to NrTableLines do
  1174. begin
  1175. Labels[0,i]:=PGtkLabel(gtk_label_new(LabelTexts[i]));
  1176. gtk_label_set_justify(Labels[0,I],GTK_JUSTIFY_RIGHT);
  1177. gtk_table_attach_defaults(Table,PgtkWidget(Labels[0,I]),0,1,I,I+1);
  1178. end;
  1179. For I:=0 to CheckboxLineStart-1 do
  1180. begin
  1181. Labels[1,i]:=PGtkLabel(gtk_label_new(''));
  1182. gtk_label_set_justify(Labels[1,I],GTK_JUSTIFY_LEFT);
  1183. gtk_table_attach_defaults(Table,PgtkWidget(Labels[1,I]),1,2,I,I+1);
  1184. end;
  1185. \end{lstlisting}
  1186. The file attributes will be represented with checkboxes:
  1187. \begin{lstlisting}{}
  1188. For I:=CheckboxLineStart to NrTableLines do
  1189. begin
  1190. checkBoxes[i]:=PgtkCheckButton(gtk_check_button_new_with_label(CheckBoxTexts[I]));
  1191. gtk_widget_set_state(PGtKWidget(CheckBoxes[i]),GTK_STATE_INSENSITIVE);
  1192. gtk_table_attach_defaults(Table,PgtkWidget(CheckBoxes[i]),1,2,I,I+1);
  1193. end;
  1194. \end{lstlisting}
  1195. The checkboxes are made inactive, so the user cannot change them.
  1196. After all labels and checkboxes are put in place, the file information
  1197. is put into various places:
  1198. \begin{lstlisting}{}
  1199. gtk_label_set_text(Labels[1,0],PChar(ExtractFileName(FileName)));
  1200. gtk_label_set_text(Labels[1,1],PChar(ExtractFilePath(FileName)));
  1201. gtk_label_set_text(Labels[1,2],PChar(ExtractFileExt(FileName)+SFile));
  1202. If FindFirst(FileName,faAnyFile,Info)=0 Then
  1203. begin
  1204. gtk_label_set_text(Labels[1,3],PChar(FileSizeToString(Info.Size)));
  1205. gtk_label_set_text(Labels[1,4],PChar(DateTimeToStr(FileDateToDateTime(Info.Time))));
  1206. For I:=CheckboxLineStart to NrTableLines do
  1207. If (CheckAttrs[i] and Info.Attr)=CheckAttrs[i] then
  1208. gtk_toggle_button_set_active(PgtkToggleButton(CheckBoxes[I]),True);
  1209. FindClose(Info);
  1210. end;
  1211. \end{lstlisting}
  1212. Finally, the 'destroy' callback for the window is set, and the OK button's
  1213. 'click' signal is attached to the destroy method of the window widget:
  1214. \begin{lstlisting}{}
  1215. gtk_signal_connect(PGtkObject(Window),'destroy',
  1216. TGTKSignalFunc(@DestroyPropDialog),Result);
  1217. gtk_signal_connect_object(PgtkObject(OKButton),'clicked',
  1218. GTK_SIGNAL_FUNC(@gtk_widget_destroy),
  1219. PGTKOBJECT(Window));
  1220. end;
  1221. end;
  1222. \end{lstlisting}
  1223. Showing the properties dialog is simple:
  1224. \begin{lstlisting}{}
  1225. Procedure ShowFilePropertiesDialog(Dialog : PFilePropertiesDialog);
  1226. begin
  1227. gtk_widget_show_all(PgtkWidget(Dialog^.Window));
  1228. end;
  1229. \end{lstlisting}
  1230. The result of all this is shown in figure \ref{fig:fileprops}.
  1231. \begin{figure}[ht]
  1232. \begin{center}
  1233. \caption{The file properties dialog.}\label{fig:fileprops}
  1234. \epsfig{file=gtk4ex/fileprops.png,width=8cm}
  1235. \end{center}
  1236. \end{figure}
  1237. The handling of the mask form is a little bit more complicated than the
  1238. properties dialog, since the mask form should return some information
  1239. to the main form.
  1240. The creation of the mask form is again a standard matter, and the reader
  1241. is referred to the code on the CD-ROM to see how it is handled. The
  1242. only thing worth noting is the handling of the click on the 'OK' button that
  1243. appears on the form.
  1244. \begin{lstlisting}{}
  1245. gtk_signal_connect(PgtkObject(OKButton),'clicked',
  1246. TGtkSignalFunc(@ApplyMask),Result);
  1247. gtk_signal_connect_object(PgtkObject(OKButton),'clicked',
  1248. GTK_SIGNAL_FUNC(@gtk_widget_destroy),
  1249. PGTKOBJECT(Window));
  1250. \end{lstlisting}
  1251. Two handlers are added to the 'clicked' signal of the 'OK' button.
  1252. The first one is pointed to a function that will apply the mask, and the
  1253. second one is redirected to the destroy method of the dialog window wigdet.
  1254. \begin{lstlisting}{}
  1255. Procedure ApplyMask(Widget : PGtkWidget; Window : PMaskForm);cdecl;
  1256. begin
  1257. With Window^ do
  1258. begin
  1259. Mask:=StrPas(gtk_entry_get_text(EMask));
  1260. If (CallBack<>Nil) then
  1261. CallBack(Mask,CallBackData);
  1262. end;
  1263. end;
  1264. \end{lstlisting}
  1265. The \lstinline|TMaskForm| record that contains fields for all widgets on
  1266. the mask entry form also contains 2 fields that allow the OK button to notify
  1267. the calling program of the new mask:
  1268. \begin{lstlisting}{}
  1269. TMaskCallBack = Procedure (Mask : String; Data : Pointer);
  1270. TMaskForm = Record
  1271. { ... widget fields ... }
  1272. Mask : ShortString;
  1273. CallBack : TMaskCallBack;
  1274. CallBackData : Pointer;
  1275. end;
  1276. PMaskForm = ^TMaskForm;
  1277. \end{lstlisting}
  1278. If the callback field is set, then the \lstinline|ApplyMask| function will call
  1279. it and pass it the new mask and some arbitrary pointer.
  1280. The main form contains a 'file mask' menu item, which has the following
  1281. 'click' handler:
  1282. \begin{lstlisting}{}
  1283. procedure DoMask(Widget : PGtkWidget ; MainForm : PMainWindow ); cdecl;
  1284. Var
  1285. S : AnsiString;
  1286. begin
  1287. With NewMaskForm^ do
  1288. begin
  1289. S:=MainForm^.FMask;
  1290. gtk_entry_set_text(EMask,PChar(S));
  1291. CallBack:=@ApplyMask;
  1292. CallBackData:=MainForm;
  1293. gtk_widget_show_all(PgtkWidget(Window));
  1294. end;
  1295. end;
  1296. \end{lstlisting}
  1297. When the user clicks the 'file mask' menu item, A mask entry form is created.
  1298. The current file mask is filled in the entry widget (\lstinline|EMask|).
  1299. The callback is set, and the callbackdata is set to the pointer to the main
  1300. window record. The callback that is executed when the user clicks the OK
  1301. button on the mask form is the following:
  1302. \begin{lstlisting}{}
  1303. Procedure ApplyMask(Mask : String; Data : Pointer);
  1304. begin
  1305. PMainWindow(data)^.FMask:=Mask;
  1306. RefreshFileView(PMainWindow(Data));
  1307. end;
  1308. \end{lstlisting}
  1309. The reason that this system of callbacks is needed is that the
  1310. \lstinline|gtk_widget_show_all| immediatly returns when the mask entry form is
  1311. shown. Even though the mask entry form dialog is a modal dialog (i.e. it alone will
  1312. respond to mouse clicks and key presses) the call returns immediatly,
  1313. there is no counterpart for the Delphi \lstinline|ShowModal| function.
  1314. When the \lstinline|gtk_widget_show_all| returns, the mask entry form is still on
  1315. the screen, so the changes made in the mask form must be communicated
  1316. back to the main form by means of a callback which is executed when
  1317. the mask entry form is closed.
  1318. The mask form in action is shown in figure \ref{fig:filemask}.
  1319. \begin{figure}[ht]
  1320. \begin{center}
  1321. \caption{The file properties dialog.}\label{fig:filemask}
  1322. \epsfig{file=gtk4ex/filemask.png,width=8cm}
  1323. \end{center}
  1324. \end{figure}
  1325. \section{Finishing the application}
  1326. In several places in this article, a reference was made to the main menu.
  1327. The main menu is created in the \lstinline|NewMainMenu| function; since
  1328. menus were discussed extensively in the previous article on programming GTK,
  1329. the code will not be presented here. The various calls developed in the
  1330. previous article have been collected in the \file{menus} unit. One
  1331. additional call was added which adds a check menuitem to a menu; the call is
  1332. similar to the regular menu item calls, and will not be discussed here.
  1333. The application is built in such a way that it can easily be extended.
  1334. Only 2 file actions have been implemented, but many more can be made.
  1335. Missing functionality includes:
  1336. \begin{itemize}
  1337. \item Renaming of files. The CList allows to put an arbitrary widget into
  1338. a cell; this functionality could be used to allow the user to change the
  1339. filename by simply editing it.
  1340. \item Moving and copying of files, using drag and drop.
  1341. \item Duplicating the main window, or spawning a new window.
  1342. \item Opening a file in another application.
  1343. \item Improve the look of the file properties form.
  1344. \item On Windows, support for showing different drives should be added.
  1345. \end{itemize}
  1346. And without doubt, many more can be found.
  1347. \end{document}