summaryrefslogtreecommitdiff
path: root/fpcdocs/gtk4.tex
blob: 1a64d26e0b50c9128d399e9f8df70a7e59391b5e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
\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}