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