mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-04-16 04:59:25 +02:00
1499 lines
58 KiB
TeX
1499 lines
58 KiB
TeX
\documentclass[10pt]{article}
|
|
\usepackage{a4}
|
|
\usepackage{epsfig}
|
|
\usepackage{listings}
|
|
\usepackage{tabularx}
|
|
\lstset{language=Delphi}%
|
|
\lstset{basicstyle=\sffamily\small}%
|
|
\lstset{commentstyle=\itshape}%
|
|
\lstset{keywordstyle=\bfseries}%
|
|
\lstset{blankstring=true}%
|
|
\newcommand{\file}[1]{\textsf{#1}}
|
|
\usepackage[pdftex]{hyperref}
|
|
\newif\ifpdf
|
|
\ifx\pdfoutput\undefined
|
|
\pdffalse
|
|
\else
|
|
\pdfoutput=1
|
|
\pdftrue
|
|
\fi
|
|
\begin{document}
|
|
\title{Programming GTK in Free Pascal: Making a real-world application.}
|
|
\author{Florian Kl\"ampfl\\and\\Micha\"el Van Canneyt}
|
|
\date{January 2001}
|
|
\maketitle
|
|
\section{Introduction}
|
|
In the third article on programming the GTK toolkit, the use of several
|
|
GTK widgets is demonstrated by building a real-world application.
|
|
|
|
The main widgets to be shown are the Toolbar, CList and Tree widgets.
|
|
Along the way, some other widgets such as a dialog will be shown as well.
|
|
|
|
The program to show all this will be a small file explorer. It will not
|
|
perform all functions that one would expect from a file explorer, but it
|
|
is not meant to be, either. It just demonstrates how one could go about when
|
|
making a file explorer.
|
|
|
|
The File explorer will have 2 main components. One is a directory tree
|
|
which can be used to select a directory. the other is a Clist, a component
|
|
that presents a list of items in a table with headings. The Clist will be
|
|
used to display the files in the directory selected in the directory tree.
|
|
|
|
The functionality included will be limited to viewing the properties of
|
|
a file, and deleting a file. The view can be customized, and sorting of
|
|
columns by clicking the column header is possible.
|
|
|
|
Each window developed in the article will be described in a record, i.e.
|
|
all window elements will have a field in a record that points to the
|
|
GTK widget used. Several forms will be developed, and each form will be
|
|
put in a separate unit. Signal callbacks will in general receive a
|
|
'userdata' pointer that points to the window record. This approach mimics
|
|
the object oriented approach of GTK, and is similar to the approach in
|
|
Delphi, where instead of a object, a window class is used.
|
|
|
|
\section{The main window}
|
|
The main window will consist of a menu, a tool bar, a directory tree and
|
|
the file list. The bottom of the screen will contain a statusbar. Between
|
|
the directory tree and the file list is a splitter that can be used to
|
|
resize the directory tree.
|
|
|
|
Right-clicking on the file list will show a popup menu, from which file
|
|
actions can be selected.
|
|
|
|
All the widgets in the main window will be stored in a big record
|
|
\lstinline|TMainWindow|:
|
|
\begin{lstlisting}{}
|
|
TMainWindow = Record
|
|
FDir,
|
|
FMask : String;
|
|
Window : PGtkWindow;
|
|
Menu : PGtkMenuBar;
|
|
Toolbar : PGtkToolBar;
|
|
DirTree : PGtkTree;
|
|
FileList : PGtkClist;
|
|
Pane : PGtkPaned;
|
|
StatusBar : PGtkStatusBar;
|
|
FilesHeader,DirHeader : PGtkLabel;
|
|
// helper objects - Menu
|
|
Accel : PGtkAccelGroup;
|
|
MFile,
|
|
MView,
|
|
MColumns,
|
|
MHelp,
|
|
// Main menu items
|
|
PMFiles : PGtkMenu;
|
|
MIFile,
|
|
MIFileProperties,
|
|
MIFileDelete,
|
|
MIExit,
|
|
MiColumns,
|
|
MIAbout,
|
|
MIHelp : PGtkMenuItem;
|
|
MIShowTitles,
|
|
MIShowExt,
|
|
MIShowSize,
|
|
MiShowDate,
|
|
MIShowAttrs : PGtkCheckMenuItem;
|
|
// Files PopupMenu Items:
|
|
PMIFileProperties,
|
|
PMIFileDelete : PGtkMenuItem;
|
|
// Packing boxes
|
|
VBox,
|
|
LeftBox,
|
|
RightBox : PGtkBox;
|
|
// Scroll boxes
|
|
TreeScrollWindow,
|
|
ListScrollWindow : PGtkScrolledWindow;
|
|
// Tree root node.
|
|
RootNode : PGtkTreeItem;
|
|
end;
|
|
PMainWindow = ^TMainWindow;
|
|
\end{lstlisting}
|
|
The record resembles a form class definition as used in \lstinline|Delphi|, it
|
|
contains all possible widgets shown on the window.
|
|
|
|
The most important ones are of course the \lstinline|DirTree| and \lstinline|FileList|
|
|
fields, the \lstinline|Menu| which will refer to the main menu and the
|
|
\lstinline|PMfiles| which will hold the popup menu. The Status bar is of course
|
|
in the \lstinline|StatusBar| field, and the \lstinline|ToolBar| field will hold the main
|
|
toolbar of the application.
|
|
|
|
The \lstinline|FDir| field will be used to hold the currently shown
|
|
directory and the \lstinline|FMask| field can be used to store a file mask that
|
|
determines what files will be shown in the list.
|
|
|
|
All these fields are filled in using the function \lstinline|NewMainForm| :
|
|
\begin{lstlisting}{}
|
|
Function NewMainForm : PMainWindow;
|
|
\end{lstlisting}
|
|
|
|
The function starts as follows :
|
|
\begin{lstlisting}{}
|
|
begin
|
|
Result:=New(PMainWindow);
|
|
With Result^ do
|
|
begin
|
|
FMask:='*.*';
|
|
Window:=PgtkWindow(gtk_window_new(GTK_WINDOW_TOPLEVEL));
|
|
gtk_window_set_title(Window,SFileExplorer);
|
|
gtk_widget_set_usize(PgtkWidget(Window),640,480);
|
|
gtk_signal_connect (PGTKOBJECT (window), 'destroy',
|
|
GTK_SIGNAL_FUNC (@destroy), Result);
|
|
gtk_widget_realize(PgtkWidget(window));
|
|
\end{lstlisting}
|
|
This is a more or less standard GTK setup for a window. Note that the
|
|
pointer to the window record is passed to the 'destroy' signal handler
|
|
for the window, and that the window widget is realized (so a actual
|
|
window is created). The necessity for the 'realize' call is explained below.
|
|
|
|
After the window is created, the main widgets on the form are created:
|
|
\begin{lstlisting}{}
|
|
Menu:=NewMainMenu(Result);
|
|
ToolBar:=NewToolbar(Result);
|
|
StatusBar:=PgtkStatusBar(gtk_statusbar_new);
|
|
FileList:=NewFileList(Result);
|
|
DirTree:=NewDirtree(Result);
|
|
PMFiles:=NewFilePopupMenu(Result);
|
|
\end{lstlisting}
|
|
|
|
The functions used to create these widgets will be discussed further on.
|
|
\begin{description}
|
|
\item[Menu] The menu is created in the function \lstinline|NewMainMenu|
|
|
\item[ToolBar] The toolbar is created in the \lstinline|NewToolbar| function.
|
|
\item[FileList] The CList component which will show the file data. Created
|
|
using \lstinline|NewFileList|.
|
|
\item[DirTree] The directory tree showing the directory structure of the
|
|
disk is created using \lstinline|NewDirtree|.
|
|
\item[PMFiles] is the popup menu for the file list and is created in the
|
|
\lstinline|NewFilePopupMenu| function.
|
|
\end{description}
|
|
Each function will set the fields which contain the helper widgets.
|
|
|
|
After the main widgets have been created, it is time to put them on the
|
|
form, and the rest of the \lstinline|NewMainForm| function is concerned
|
|
mainly with placing the widgets in appropriate containers.
|
|
|
|
A splitter widget in GTK is called a \lstinline|paned window|. It can be created
|
|
using one of the following functions:
|
|
\begin{lstlisting}{}
|
|
function gtk_hpaned_new : PGtkWidget;
|
|
function gtk_vpaned_new : PGtkWidget;
|
|
\end{lstlisting}
|
|
Since the directory tree and file explorer window will be located left to
|
|
each other, a \lstinline|gtk_hpaned_new| call is needed for the file explorer.
|
|
|
|
The \lstinline|paned window| has 2 halves, in each of which a widget can be
|
|
placed. This is done using the following calls:
|
|
\begin{lstlisting}{}
|
|
procedure gtk_paned_add1(paned:PGtkPaned; child:PGtkWidget);cdecl;
|
|
procedure gtk_paned_add2(paned:PGtkPaned; child:PGtkWidget);cdecl;
|
|
\end{lstlisting}
|
|
The first function adds a widget to the left pane, the second to the right
|
|
pane (or the top and bottom panes if the splitter is vertical).
|
|
|
|
With this knowledge, the Directory Tree and File List can be put on the
|
|
form. In the case of the file explorer, 2 widgets will be packed in vertical
|
|
boxes which are on their turn put the left and right panes of the splitter:
|
|
\begin{lstlisting}{}
|
|
Pane:=PgtkPaned(gtk_hpaned_new);
|
|
DirHeader:=PgtkLabel(gtk_label_new(pchar(SDirTree)));
|
|
LeftBox:=PGtkBox(gtk_vbox_new(false,0));
|
|
gtk_box_pack_start(Leftbox,PGtkWidget(DirHeader),False,False,0);
|
|
gtk_box_pack_start(Leftbox,PgtkWidget(TreeScrollWindow),true,True,0);
|
|
gtk_paned_add1(pane,PGtkWidget(Leftbox));
|
|
\end{lstlisting}
|
|
The left-hand side vertical box (\lstinline|LeftBox|) contains a label
|
|
(\lstinline|DirHeader|) which serves as a heading for the directory tree (\lstinline|DirTree|).
|
|
It displays a static text (in the constant \lstinline|SDirTree|).
|
|
|
|
The right pane can be filled in a similar way with the file list:
|
|
\begin{lstlisting}{}
|
|
FilesHeader:=PgtkLabel(gtk_label_new(pchar(SFilesInDir)));
|
|
RightBox:=PGtkBox(gtk_vbox_new(false,0));
|
|
gtk_box_pack_start(Rightbox,PGtkWidget(FilesHeader),False,False,0);
|
|
gtk_box_pack_start(Rightbox,PGtkWidget(ListScrollWindow),true,True,0);
|
|
gtk_paned_add2(pane,PGtkWidget(Rightbox));
|
|
\end{lstlisting}
|
|
The right-hand side vertical box contains a label \lstinline|FileHeader|
|
|
which serves as a heading for the file list (\lstinline|FileList|).
|
|
It will be used to display the current directory name
|
|
(\lstinline|SFilesInDir| constant).
|
|
|
|
After the directory tree and file view have been put in a paned window,
|
|
all that is left to do is to stack the statusbar, paned window, toolbar
|
|
and menu in a vertical box \lstinline|VBox| which covers the whole window:
|
|
\begin{lstlisting}{}
|
|
VBox:=PGtkBox(gtk_vbox_new(false,0));
|
|
gtk_container_add(PGtkContainer(Window),PgtkWidget(VBox));
|
|
gtk_box_pack_start(vbox,PGtkWidget(Menu),False,False,0);
|
|
gtk_box_pack_start(vbox,PGtkWidget(ToolBar),False,False,0);
|
|
gtk_box_pack_start(vbox,PGtkWidget(Pane),true,true,0);
|
|
gtk_box_pack_start(vbox,PGtkWidget(StatusBar),false,false,0);
|
|
gtk_widget_show_all(PGtkWidget(vbox));
|
|
end;
|
|
end;
|
|
\end{lstlisting}
|
|
The destroy signal of the window does nothing except destroying the
|
|
main window record and telling GTK to exit the event loop:
|
|
\begin{lstlisting}{}
|
|
procedure destroy(widget : pGtkWidget ; Window : PMainWindow); cdecl;
|
|
begin
|
|
gtk_clist_clear(Window^.FileList);
|
|
dispose(Window);
|
|
gtk_main_quit();
|
|
end;
|
|
\end{lstlisting}
|
|
The call to \lstinline|gtk_clist_clear| serves to clear the file list window.
|
|
The necessity for this call will be explained below.
|
|
|
|
\section{The file list}
|
|
The file list is constructed using the GTK CList widget. This is a powerful
|
|
widget that contains a lot of functionality, comparable to the
|
|
\lstinline|TListView| component found in Delphi.
|
|
|
|
A the file list widget is created using the following function:
|
|
\begin{lstlisting}{}
|
|
Function NewFileList(MainWindow : PMainWindow) : PGtkClist;
|
|
|
|
Const
|
|
Titles : Array[1..6] of pchar =
|
|
('Name','ext','Size','Date','Attributes','');
|
|
|
|
begin
|
|
MainWindow^.ListScrollWindow:=
|
|
PGtkScrolledWindow(gtk_scrolled_window_new(Nil,Nil));
|
|
gtk_scrolled_window_set_policy(MainWindow^.ListScrollWindow,
|
|
GTK_POLICY_AUTOMATIC,
|
|
GTK_POLICY_AUTOMATIC);
|
|
Result:=PGtkClist(Gtk_Clist_New_with_titles(6,@Titles));
|
|
gtk_Container_add(PGTKContainer(MainWindow^.ListScrollWindow),
|
|
PGtkWidget(Result));
|
|
\end{lstlisting}
|
|
A Clist object is not capable of drawing scroll bars if it contains too many
|
|
items for its size, so first a \lstinline|Scrolled Window| is created in which
|
|
the Clist object is embedded. A scrolled window is a container widget which
|
|
does nothing except providing scrollbars for the widget it contains.
|
|
|
|
A scrolled window is created using the \lstinline|gtk_scrolled_window_new|
|
|
function:
|
|
\begin{lstlisting}{}
|
|
function gtk_scrolled_window_new(hadjustment:PGtkAdjustment;
|
|
vadjustment:PGtkAdjustment):PGtkWidget
|
|
\end{lstlisting}
|
|
The \lstinline|Adjustment| parameters can be used to pass scrollbar widgets
|
|
that the scrolled window should use to do it's work.
|
|
If none are passed, the scrolled window will create the needed scrollbars
|
|
by itself.
|
|
|
|
The visibility of the scrollbars can be controlled with the policy property
|
|
of the scrolled window:
|
|
\begin{lstlisting}{}
|
|
gtk_scrolled_window_set_policy(scrolled_window:PGtkScrolledWindow;
|
|
hscrollbar_policy:TGtkPolicyType;
|
|
vscrollbar_policy:TGtkPolicyType)
|
|
\end{lstlisting}
|
|
The horizontal and vertical policies can be set to the following values:
|
|
\begin{description}
|
|
\item[GTK\_POLICY\_AUTOMATIC] Scrollbars are only visible if they are needed.
|
|
\item[GTK\_POLICY\_ALWAYS] Scrollbars are always visible.
|
|
\end{description}
|
|
|
|
After the creation of the scrolled window, the file list is created and
|
|
added to the scrolled window. A CList widget can be created using 2 calls;
|
|
\begin{lstlisting}{}
|
|
function gtk_clist_new (columns:gint):PGtkWidget;
|
|
function gtk_clist_new_with_titles (columns:gint;
|
|
titles:PPgchar):PGtkWidget;
|
|
\end{lstlisting}
|
|
In both cases, the number of columns in the list must be passed. If
|
|
the column header titles are fixed and known, they can be passed in the
|
|
\lstinline|gtk_clist_new_with_titles| call, but they can still be set and
|
|
retrieved later on with the following calls:
|
|
\begin{lstlisting}{}
|
|
Procedure gtk_clist_set_column_title(clist:PGtkCList;
|
|
column:gint;
|
|
title:Pgchar);cdecl;
|
|
function gtk_clist_get_column_title(clist:PGtkCList;
|
|
column:gint):Pgchar;cdecl;
|
|
\end{lstlisting}
|
|
Note that the column indices are 0 based.
|
|
|
|
After the CList widget has been created, some properties can be set:
|
|
\begin{lstlisting}{}
|
|
gtk_clist_set_shadow_type(Result,GTK_SHADOW_ETCHED_OUT);
|
|
\end{lstlisting}
|
|
This call sets the border around the clist. The possible values for
|
|
the last parameter (the \lstinline|TGtkShadowType|) of
|
|
\lstinline|gtk_clist_set_shadow_type| are:
|
|
\begin{description}
|
|
\item[GTK\_SHADOW\_NONE] No border.
|
|
\item[GTK\_SHADOW\_IN] the clist appears lowered.
|
|
\item[GTK\_SHADOW\_OUT] the clist appears raised.
|
|
\item[GTK\_SHADOW\_ETCHED\_IN] the clist appears with a lowered frame.
|
|
\item[GTK\_SHADOW\_ETCHED\_OUT] the clist appears with a raised frame.
|
|
\end{description}
|
|
|
|
The justification of a column in the list can be set:
|
|
\begin{lstlisting}{}
|
|
gtk_clist_set_column_justification(result,2,GTK_JUSTIFY_RIGHT);
|
|
\end{lstlisting}
|
|
column 2 will contain the file sizes, so it is set right-justified.
|
|
Other possible values are for justification are
|
|
\lstinline|GTK_JUSTIFY_LEFT|, \lstinline|GTK_JUSTIFY_CENTER|, and
|
|
\lstinline|GTK_JUSTIFY_FILL|, which have their obvious meanings.
|
|
|
|
To be able to select multiple items (or rows) at once, the selection mode of
|
|
the CList must be set:
|
|
\begin{lstlisting}{}
|
|
gtk_clist_set_selection_mode(Result,GTK_SELECTION_MULTIPLE);
|
|
\end{lstlisting}
|
|
Possible modes of selection are:
|
|
\begin{description}
|
|
\item[GTK\_SELECTION\_SINGLE] Only one row can be selected at any given
|
|
time.
|
|
\item[GTK\_SELECTION\_BROWSE] Multiple items can be selected, however the
|
|
selection will always return 1 item.
|
|
\item[GTK\_SELECTION\_MULTIPLE] Multiple items can be selected, and the
|
|
selection will contain all selected items.
|
|
\item[GTK\_SELECTION\_EXTENDED] The selection is always \lstinline|Nil|.
|
|
\end{description}
|
|
The selection is a field (\lstinline|selection|) of type \lstinline|PGList| in the
|
|
\lstinline|TGtkCList| record. A \lstinline|PGlist| is a pointer to a doubly linked
|
|
list with data pointers. More details about this will follow.
|
|
|
|
The elements in the list list can be sorted.
|
|
\begin{lstlisting}{}
|
|
gtk_clist_set_auto_sort(Result,True);
|
|
If DefCompare=Nil then
|
|
DefCompare:=Result^.compare;
|
|
gtk_clist_set_compare_func(Result,
|
|
TGtkCListCompareFunc(@FileCompareFunc));
|
|
\end{lstlisting}
|
|
By default, a CList sorts by comparing the texts in the current sort column
|
|
of the items in the list. This sorting happens using the \lstinline|compare|
|
|
function of the CList. The standard \lstinline|compare| function of the list
|
|
is saved here in a variable \lstinline|DefCompare|, so it can still be used.
|
|
Using the \lstinline|gtk_clist_set_compare_func| the compare function to be
|
|
used when sorting can be set, and it is set to the function
|
|
\lstinline|FileCompareFunc|, which will be discussed later on.
|
|
|
|
The \lstinline|gtk_clist_set_auto_sort| can be used to set the auto-sort
|
|
feature of the Clist. If auto-sort is on, adding new items to the CList will
|
|
insert them in the correct order. If auto-sort is off, new items are
|
|
appended to the beginning or end of the list.
|
|
|
|
After the sort function is set, handlers are attached to 2 signals:
|
|
\begin{lstlisting}{}
|
|
gtk_signal_connect(PgtkObject(Result),'button_press_event',
|
|
TGtkSignalFunc(@ShowPopup),MainWindow);
|
|
gtk_signal_connect(PgtkObject(Result),'click_column',
|
|
TGtkSignalFunc(@FileColumnClick),MainWindow);
|
|
\end{lstlisting}
|
|
The first handler connects to a mouse button press event. This will be used
|
|
to detect a right mouse click, and to show a popup menu:
|
|
\begin{lstlisting}{}
|
|
Procedure ShowPopup(Widget : PGtkWidget;
|
|
Event : PGdkEventButton;
|
|
Window : PMainWindow);cdecl;
|
|
|
|
begin
|
|
if (event^.thetype=GDK_BUTTON_PRESS) and
|
|
(event^.button=3) then
|
|
gtk_menu_popup(Window^.PMFiles,Nil,Nil,Nil,NIl,3,event^.time);
|
|
end;
|
|
\end{lstlisting}
|
|
The \lstinline|gtk_menu_popup| function does nothing but showing the menu;
|
|
when a menu item is clicked, the menu will close by itself.
|
|
|
|
The second handler connects to the 'click\_column' event. This event is
|
|
emitted if the user clicks on the column header. It will be used to switch
|
|
the sort order of the file list:
|
|
\begin{lstlisting}{}
|
|
Procedure FileColumnClick(List : PGtkCList;Column:gint; Window : PMainWindow);cdecl;
|
|
|
|
Var
|
|
I : longint;
|
|
NS : TGtkSortType;
|
|
|
|
begin
|
|
If Column<>List^.sort_column Then
|
|
begin
|
|
gtk_clist_set_sort_type(List,GTK_SORT_ASCENDING);
|
|
gtk_clist_set_sort_column(list,Column);
|
|
end
|
|
else
|
|
begin
|
|
If (List^.Sort_type=GTK_SORT_ASCENDING) Then
|
|
NS:=GTK_SORT_DESCENDING
|
|
else
|
|
NS:=GTK_SORT_ASCENDING;
|
|
gtk_clist_set_sort_type(List,NS);
|
|
end;
|
|
gtk_clist_sort(list);
|
|
end;
|
|
\end{lstlisting}
|
|
The function starts by retrieving the current sort column. If it is
|
|
different from the column the used clicked on, then 2 things are done:
|
|
\begin{enumerate}
|
|
\item The sort type is set to ascending.
|
|
\item The sort column is set to the column the user clicked.
|
|
\end{enumerate}
|
|
If, on the other hand, the user clicks on a column that is the sort column,
|
|
the sort type is simply reversed. After the sort column and sort type are
|
|
set, the list is epxlicitly sorted. (neither of the calls that set the sort
|
|
order or sort column forces a sort).
|
|
|
|
The sort happens using the \lstinline|compare| function (\lstinline|FileCompareFunc|)
|
|
that was set when the CList was created:
|
|
\begin{lstlisting}{}
|
|
Function FileCompareFunc(List:PGtkCList; Row1,Row2 : PGtkCListRow) : Longint; Cdecl;
|
|
|
|
Var
|
|
SC : Longint;
|
|
|
|
begin
|
|
SC:=List^.sort_column;
|
|
If SC in [2,3] then
|
|
begin
|
|
SC:=SC-2;
|
|
Result:=PLongint(Row1^.Data)[SC]-PLongint(Row2^.Data)[SC];
|
|
end
|
|
Else
|
|
Result:=DefCompare(List,Row1,Row2);
|
|
end;
|
|
\end{lstlisting}
|
|
This function receives 3 arguments:
|
|
\begin{itemize}
|
|
\item The list that needs to be sorted.
|
|
\item 2 pointers to the row objects that must be compared.
|
|
\end{itemize}
|
|
The result must be an integer that is negative if the first row should come
|
|
before the second or larger than zero if the second row should come before
|
|
the first. If the result is zero then the columns are considered the same.
|
|
|
|
The function checks what the sort column is. If it is not the size (2) or
|
|
date (3) column, then the default row compare function (which was saved in
|
|
the \lstinline|DefCompare| variable when the list was created) is used to
|
|
compare the rows. If the size or date columns must be compared, the user
|
|
data associated with the rows is examined. As will be shown below, the user
|
|
data will point to an array of 2 Longint values that describe the size and
|
|
datestamp of the file. The approriate values are compared and the result is
|
|
passed back.
|
|
|
|
To fill the file list with data, the \lstinline|FillList| function is
|
|
implemented:
|
|
\begin{lstlisting}{}
|
|
Function FillList(List : PGtkCList;
|
|
Const Dir,Mask : String) : Integer;
|
|
|
|
Var
|
|
Info : TSearchRec;
|
|
Size : Int64;
|
|
I,J : longint;
|
|
|
|
begin
|
|
Result:=0;
|
|
Size:=0;
|
|
gtk_clist_freeze(List);
|
|
Try
|
|
gtk_clist_clear(List);
|
|
If FindFirst (AddTrailingSeparator(Dir)+Mask,
|
|
faAnyFile,Info)=0 then
|
|
Repeat
|
|
Inc(Size,Info.Size);
|
|
AddFileToList(List,Info);
|
|
Inc(Result);
|
|
Until FindNext(Info)<>0;
|
|
FindClose(info);
|
|
finally
|
|
For I:=0 to 4 do
|
|
begin
|
|
J:=gtk_clist_optimal_column_width(List,i);
|
|
gtk_clist_set_column_width(List,i,J);
|
|
end;
|
|
gtk_clist_thaw(List)
|
|
end;
|
|
end;
|
|
\end{lstlisting}
|
|
This function is very straightforward. To start, it 'freezes' the list with
|
|
\lstinline|gtk_clist_freeze|; this prevents the list from updating the
|
|
screen each time a row is added or deleted. Omitting this call would cause
|
|
serious performance degradation and screen flicker.
|
|
|
|
After freezing the list, it is cleared; Then a simple loop is implemented
|
|
that scans the given directory with the given file mask using the
|
|
\lstinline|FindFirst|/\lstinline|FindNext| calls. For each file found
|
|
it calls the \lstinline|AddFileToList| function, that will actually add the
|
|
file to the list view, using the information found in the search record.
|
|
|
|
The \lstinline|AddTrailingSeparator| adds a directory separator to a
|
|
string containing the name of a directory if this does not end on a
|
|
separator yet. It can be found in the \file{futils} unit.
|
|
|
|
After the loop has finished, the optimal width for each column is
|
|
retrieved using the \lstinline|gtk_clist_optimal_column_width| function
|
|
and the result is used to set the column width. As a result, the columns will
|
|
have the correct size for displaying all items.
|
|
|
|
When this has been done, the list is 'thawed' with \lstinline|gtk_clist_thaw|,
|
|
which means that it will repaint itself if needed. This happens in a
|
|
\lstinline|finally| block since the \lstinline|gtk_clist_freeze| and
|
|
\lstinline|gtk_clist_thaw| work with a reference counter. For each 'freeze'
|
|
call the counter is increased. It is decreased with a 'thaw' call. When the
|
|
counter reaches zero, the list is updated.
|
|
|
|
The function that actually adds a row to the list view is quite simple:
|
|
\begin{lstlisting}{}
|
|
Procedure AddFileToList(List : PGtkCList; Info : TSearchRec);
|
|
|
|
Var
|
|
Texts : Array[1..6] of AnsiString;
|
|
FSD : PLongint;
|
|
I : longint;
|
|
|
|
begin
|
|
Texts[1]:=ExtractFileName(Info.Name);
|
|
Texts[2]:=ExtractFileExt(Info.Name);
|
|
Texts[3]:=FileSizeToString(Info.Size);
|
|
Texts[4]:=DateTimeToStr(FileDateToDateTime(Info.Time));
|
|
Texts[5]:=FileAttrsToString(Info.Attr);
|
|
Texts[6]:='';
|
|
i:=gtk_clist_append(List,@Texts[1]);
|
|
FSD:=GetMem(2*SizeOf(Longint));
|
|
FSD[0]:=Info.Size;
|
|
FSD[1]:=Info.Time;
|
|
gtk_clist_set_row_data_full (List,I,FSD,@DestroySortData);
|
|
end;
|
|
\end{lstlisting}
|
|
The \lstinline|gtk_clist_append| call accepts 2 paramers: a CList, and a
|
|
pointer to an array of zero-terminated strings. The array must contain as
|
|
much items as the CList has columns (in the above, the last column is
|
|
always empty, as this gives a better visual effect). The call adds a column
|
|
at the end of a list; An item can be inserted at the beginning of the list
|
|
with \lstinline|gtk_clist_append|, which accepts the same parameters. An
|
|
item can be inserted at certain position:
|
|
\begin{lstlisting}{}
|
|
gtk_clist_insert(clist:PGtkCList; row:gint; thetext:PPgchar);cdecl;
|
|
\end{lstlisting}
|
|
Note that all these calls do the same thing if the 'auto sort' was set for
|
|
the CList.
|
|
|
|
The \lstinline|FileAttrsToString| function converts file attributes to a
|
|
string of characters that indicate whether a given attribute is present.
|
|
It can be found in the \file{futils} unit and will not be shown here.
|
|
|
|
After the file data was appended to the CList, an array of 2 longints is
|
|
allocated on the heap. The first longint is filled with the size of the
|
|
file, the second with the date of the file. The pointer to this array is
|
|
then associated with the row that was just inserted with the
|
|
\lstinline|gtk_clist_set_row_data_full| call. There are 2 calls to
|
|
associate data with a row:
|
|
\begin{lstlisting}{}
|
|
gtk_clist_set_row_data(clist:PGtkCList;
|
|
row:gint;
|
|
data:gpointer);cdecl;
|
|
gtk_clist_set_row_data_full(clist:PGtkCList;
|
|
row:gint; data:gpointer;
|
|
destroy: :TGtkDestroyNotify);
|
|
\end{lstlisting}
|
|
the first call is used to add data to a clist that will not need to be
|
|
destroyed if the row is deleted. The second call can be used to pass a
|
|
callback that will be called when the row is destroyed.
|
|
|
|
In the case of the file list, the \lstinline|DestroySortData| call is
|
|
used to dispose the array with sort data:
|
|
\begin{lstlisting}{}
|
|
Procedure DestroySortData(FSD : Pointer);cdecl;
|
|
|
|
begin
|
|
FreeMem(FSD);
|
|
end;
|
|
\end{lstlisting}
|
|
The reason that the file list is cleared when the main window is destroyed
|
|
now becomes apparent: when the list is cleared, all data associated with
|
|
the file list is freed. If the call to \lstinline|gtk_clist_clear| is
|
|
omitted before destroying the main window, the list is not cleared and all
|
|
data stays in memory even after the window closes.
|
|
|
|
The display of the column titles of the file list can be switched on or off.
|
|
To do this a check menu item ('Hide titles') is added to the 'View' menu.
|
|
If the menu is clicked, the following callback is executed:
|
|
\begin{lstlisting}{}
|
|
Procedure ToggleFileListTitles(Sender : PGtkCheckMenuItem;
|
|
Window : PMainWindow);cdecl;
|
|
|
|
begin
|
|
If active(Sender^)=0 then
|
|
gtk_clist_column_titles_show(Window^.FileList)
|
|
else
|
|
gtk_clist_column_titles_hide(Window^.FileList)
|
|
end;
|
|
\end{lstlisting}
|
|
The \lstinline|active| function checks whether a check menu item is currently
|
|
checked ot not and shows or hides the titles.
|
|
|
|
Not only can the column titles be switched on or off, it is also possible to
|
|
control whether or not a given column must be shown;
|
|
|
|
Under the 'View' menu, there is a 'Hide columns' submenu that contains 4
|
|
check menus that can be used to toggle the visibility of the columns in the
|
|
file list. All the check menu items are connected to the following callback:
|
|
\begin{lstlisting}{}
|
|
Procedure ToggleFileListColumns(Sender : PGtkCheckMenuItem;
|
|
Window : PMainWindow);cdecl;
|
|
|
|
Var Col : Longint;
|
|
|
|
begin
|
|
With Window^ do
|
|
If Sender=MIShowExt Then
|
|
Col:=1
|
|
else if Sender=MiShowSize Then
|
|
Col:=2
|
|
else if Sender=MIShowDate then
|
|
Col:=3
|
|
else
|
|
Col:=4;
|
|
gtk_clist_set_column_visibility(Window^.FileList,
|
|
Col,
|
|
(Active(Sender^)=0));
|
|
end;
|
|
\end{lstlisting}
|
|
The call gets as 'user data' a pointer to the main window record. Using this
|
|
it checks which menu emitted the call, and updates the corresponding column
|
|
with the \lstinline|gtk_clist_set_column_visibility| function.
|
|
|
|
More attributes of a CList can be set, but they will not be discussed here;
|
|
the GTK documentation and tutorial offer an overview of the possibilities.
|
|
|
|
The selection mode of the CList has been set to allow selection of multiple
|
|
rows. The Clist maintains a linked list (A Glist) with the rows that are
|
|
part of the selection. The linked list contains the indexes of the selected
|
|
rows in it's associated data.
|
|
|
|
The linked list \lstinline|Glist| is often used in GTK applications.
|
|
It consists of the following records:
|
|
\begin{lstlisting}{}
|
|
TGList = record
|
|
data gpointer;
|
|
next,prev : PGlist;
|
|
end;
|
|
PGlist=^TGlist;
|
|
\end{lstlisting}
|
|
The selection of a CList is of type \lstinline|PGlist|. The \lstinline|data|
|
|
pointer can be typecasted to an integer to return the index of a selected
|
|
row.
|
|
|
|
The following function walks the selection linked list and stores the
|
|
associated filenames in a \lstinline|TStrings| class:
|
|
\begin{lstlisting}{}
|
|
Procedure GetFileSelection (List : PGtkClist; Selection : TStrings);
|
|
|
|
Var
|
|
SList : PGList;
|
|
Index : Longint;
|
|
P : PChar;
|
|
|
|
begin
|
|
Selection.Clear;
|
|
Slist:=List^.Selection;
|
|
While SList<>nil do
|
|
begin
|
|
Index:=Longint(SList^.Data);
|
|
gtk_clist_get_text(List,Index,0,@p);
|
|
Selection.Add(StrPas(p));
|
|
SList:=g_list_next(SList);
|
|
end;
|
|
end;
|
|
\end{lstlisting}
|
|
The \lstinline|gtk_clist_get_text| retrieves the text of a given cell in the
|
|
CList (a similar function exists to set the text) , and the
|
|
\lstinline|g_list_next| jumps to the next element in the linked list.
|
|
|
|
The \lstinline|TStrings| class is the standard string container as defined
|
|
in the \lstinline|Classes| unit of Free Pascal (or Delphi).
|
|
|
|
The above function will be used to retrieve the list of selected files so
|
|
operations can be done on the selection.
|
|
|
|
To retrieve the first (and possibly only) item of a selection, and the
|
|
number of items in a selection, the following functions can be used:
|
|
\begin{lstlisting}{}
|
|
Function GetFileFirstSelection (List : PGtkClist) : String;
|
|
|
|
Var
|
|
SList : PGList;
|
|
Index : Longint;
|
|
P : PChar;
|
|
|
|
begin
|
|
Result:='';
|
|
Slist:=List^.Selection;
|
|
If SList<>nil then
|
|
begin
|
|
Index:=Longint(SList^.Data);
|
|
gtk_clist_get_text(List,Index,0,@p);
|
|
Result:=StrPas(p);
|
|
end;
|
|
end;
|
|
|
|
Function GetFileSelectionCount (List : PGtkClist) : Longint;
|
|
|
|
Var
|
|
SList : PGList;
|
|
|
|
begin
|
|
Slist:=List^.Selection;
|
|
Result:=0;
|
|
While SList<>nil do
|
|
begin
|
|
Inc(Result);
|
|
SList:=g_list_next(SList);
|
|
end;
|
|
end;
|
|
\end{lstlisting}
|
|
These functions will be used further on.
|
|
|
|
The filelist is now ready to be used. To be able to select a directory from
|
|
which the files should be displayed, a Tree widget is used. How to create
|
|
this tree and connect it to the file list is explained in the next section.
|
|
|
|
\section{The directory tree}
|
|
The directory tree will allow the user to browse through the directories on
|
|
his system. When a directory is selected, the file view should be updated
|
|
to show the files in the selected directory.
|
|
|
|
To make the directory tree more efficient and less memory consuming, the
|
|
tree is not filled with the whole directory tree at once. Instead, only 2
|
|
levels of directories will be put in the tree. The tree is progessively
|
|
filled as the user expands the directory nodes.
|
|
|
|
The directory tree is created in the following function:
|
|
\begin{lstlisting}{}
|
|
Function NewDirtree (MainWindow : PMainWindow) : PGtkTree;
|
|
|
|
begin
|
|
Result:=PGtkTree(gtk_tree_new());
|
|
With MainWindow^ do
|
|
begin
|
|
TreeScrollWindow:=PGtkScrolledWindow(gtk_scrolled_window_new(Nil,Nil));
|
|
gtk_widget_show(PGtkWidget(TreeScrollWindow));
|
|
gtk_scrolled_window_set_policy(TreeScrollWindow,
|
|
GTK_POLICY_AUTOMATIC,
|
|
GTK_POLICY_AUTOMATIC);
|
|
gtk_scrolled_window_add_with_viewport(TreeScrollWindow,PGtkWidget(Result));
|
|
RootNode:=PGtkTreeItem(gtk_tree_Item_new_with_label(Pchar(PathSeparator)));
|
|
gtk_tree_append(Result,PgtkWidget(RootNode));
|
|
scandirs(PathSeparator,Result, RootNode,True,MainWindow);
|
|
gtk_tree_item_expand(rootnode);
|
|
end;
|
|
end;
|
|
\end{lstlisting}
|
|
The function starts off by creating the tree widget which is the return
|
|
value of the function.
|
|
|
|
Similar to the Clist, the tree widget does not possess functionality
|
|
for displaying scroll bars, so a 'scrolled window' is created,
|
|
in which the tree widget is placed.
|
|
|
|
A tree can have one or more tree items connected to it. Each of these tree
|
|
items can in turn have a tree associated with it, which in turn can again
|
|
have tree items associated. This way the tree is recursively constructed.
|
|
|
|
The directory tree is filled with 1 tree item, which will represent the root
|
|
directory of the disk which is browsed with the file explorer; The
|
|
\lstinline|gtk_tree_item_new_with_label| call returns a new tree item,
|
|
which is then appended to the tree using the \lstinline|gtk_tree_append|
|
|
call.
|
|
|
|
After this is done, the directories below the root directory are scanned and
|
|
appended to the root node in the \lstinline|scandirs| function, explained
|
|
below. If the root node was filled, then it is expanded with
|
|
\lstinline|gtk_tree_item_expand| (it can be collapsed with
|
|
\lstinline|gtk_tree_item_collapse|)
|
|
|
|
The \lstinline|scandirs| function scans a given directory for subdirectories
|
|
and appends each directory to a subtree of a given node. The subtree is
|
|
created if needed:
|
|
\begin{lstlisting}{}
|
|
Procedure Scandirs(Path: String; Tree : PgtkTree;
|
|
Node: PGtkTreeItem ; SubSub : Boolean;
|
|
Window : PMainWindow);
|
|
|
|
Var
|
|
NewTree : PGtkTree;
|
|
NewNode : PGtkTreeItem;
|
|
Info : TSearchRec;
|
|
S,FP : AnsiString;
|
|
|
|
begin
|
|
NewTree:=Nil;
|
|
FP:=AddTrailingSeparator(Path);
|
|
If FindFirst(FP+'*.*',faAnyfile,Info)=0 then
|
|
Try
|
|
repeat
|
|
If ((Info.Attr and faDirectory)=faDirectory) then
|
|
begin
|
|
S:=Info.Name;
|
|
If (S<>'.') and (S<>'..') then
|
|
begin
|
|
If (Node<>Nil) then
|
|
begin
|
|
If (NewTree=Nil) and (node<>Nil) then
|
|
begin
|
|
NewTree:=PGtkTree(gtk_tree_new);
|
|
gtk_tree_item_set_subtree(Node,PGtkWidget(NewTree));
|
|
end
|
|
end
|
|
else
|
|
NewTree:=Tree;
|
|
NewNode:=PGtkTreeItem(gtk_tree_item_new_with_label(Pchar(S)));
|
|
gtk_tree_append(NewTree,PgtkWidget(NewNode));
|
|
gtk_signal_connect(PGtkObject(NewNode),'select',
|
|
TGtkSignalFunc(@DirSelect),Window);
|
|
gtk_signal_connect(PGtkObject(NewNode),'expand',
|
|
TGtkSignalFunc(@DirExpand),Window);
|
|
If SubSub then
|
|
ScanDirs(FP+S,Tree,NewNode,False,Window);
|
|
gtk_widget_show(PGtkWidget(NewNode));
|
|
end;
|
|
end;
|
|
until FindNext(Info)<>0;
|
|
Finally
|
|
FindClose(Info);
|
|
end;
|
|
gtk_widget_show(PGtkWidget(Node));
|
|
end;
|
|
\end{lstlisting}
|
|
The routine is a simple loop. If a subdirectory is found then a new
|
|
tree widget is created (\lstinline|newTree|) and appended to the
|
|
given node with the \lstinline|gtk_tree_item_set_subtree| call.
|
|
|
|
For each found subdirectory a new treeitem is created and appended to
|
|
the subtree. 2 signals handlers are connected to the created tree item,
|
|
one for 'select' signal which is emitted when the user selects a tree item,
|
|
and one for the 'expand' signal which is emitted when the user expands a
|
|
node. Each of these handlers gets as data a pointer to the main window
|
|
record.
|
|
|
|
The \lstinline|SubSub| parameter is used to control the recursive behaviour.
|
|
If it is set to \lstinline|True|, the \lstinline|Scandirs| function will
|
|
call itself recursively, but only once. As a result only 2 levels of
|
|
subdirectories are scanned.
|
|
|
|
Finally, the created nodes are shown.
|
|
|
|
When the user expands a node, the \lstinline|DirExpand| function is
|
|
called:
|
|
\begin{lstlisting}{}
|
|
Procedure DirExpand(Item : PGtkTreeItem; Window : PMainWindow);cdecl;
|
|
|
|
Var
|
|
Dir : String;
|
|
SubTree : PGtkTree;
|
|
SubNodes : PGList;
|
|
Node : PGtkTreeItem;
|
|
|
|
begin
|
|
SubTree:=PgtkTree(Item^.SubTree);
|
|
SubNodes:=gtk_container_children(PGtkContainer(SubTree));
|
|
While SubNodes<>Nil do
|
|
begin
|
|
Node:=PgtkTreeItem(SubNodes^.Data);
|
|
If (Node^.SubTree<>Nil) then
|
|
gtk_tree_item_remove_subtree(Node);
|
|
Scandirs(GetPathName(Node),Nil,Node,False,Window);
|
|
SubNodes:=g_list_remove_link(SubNodes,SubNodes);
|
|
end;
|
|
end;
|
|
\end{lstlisting}
|
|
The function starts by retrieving the subtree of the tree item that
|
|
triggered the callback. It then retrieves the list of subnodes (treeitems)
|
|
of the subtree which represent the subdirectories of the directory node
|
|
that is about to be expanded. The Tree object descends from the GTK
|
|
container object, and keeps its treeitems in the container's children
|
|
list. This list is a Glist. The \lstinline|gtk_container_children| returns
|
|
a copy of the list containing the children.
|
|
|
|
Then a simple loop is executed: for each of
|
|
the found nodes, the subtree is destroyed if it exists:
|
|
\lstinline|gtk_tree_item_remove_subtree| removes a subtree from a treeItem
|
|
and destroys it.
|
|
|
|
After the subtree is destroyed, at the subirectory is scanned for possible
|
|
subdirecties (remark that the \lstinline|SubSub| parameter is set to
|
|
\lstinline|false|) and the subtree is recreated if needed.
|
|
|
|
The directory corresponding to a given node is calculated in the
|
|
\lstinline|GetPathName| function, explained below.
|
|
|
|
The next cycle of the loop is started by removing and destroying the first
|
|
element of the GList with the \lstinline|g_list_remove_link| call:
|
|
the call returns the new start of the list with the element removed. By
|
|
passing the first element of the list as the element to be removed the
|
|
whole list is traversed.
|
|
|
|
When the user selects a tree item, the list view must be updated with
|
|
the files in that directory. This is done in the \lstinline|DirSelect|
|
|
handler for the 'select' signal:
|
|
\begin{lstlisting}{}
|
|
Procedure DirSelect(Item : PGtkTreeItem; Window : PMainWindow);cdecl;
|
|
|
|
begin
|
|
ShowDir(Window,GetPathName(Item));
|
|
end;
|
|
|
|
Procedure ShowDir (Window : PMainWindow; Dir : String);
|
|
|
|
begin
|
|
With Window^ do
|
|
begin
|
|
FDir:=Dir;
|
|
FillList(FileList,Dir,FMask);
|
|
gtk_label_set_text(FilesHeader,pchar(Format(SFilesInDir,[Dir])));
|
|
end;
|
|
end;
|
|
|
|
\end{lstlisting}
|
|
The \lstinline|Showdir| function will be called from other places as
|
|
well hence it is put separately; The \lstinline|DirSelect| function
|
|
does nothing but to call the ShowDir function after it has calculated the
|
|
path of the treeitem that triggered the 'select' signal:
|
|
\begin{lstlisting}{}
|
|
Function GetPathName(Item : PGtkTreeItem) : String;
|
|
|
|
Var P : PChar;
|
|
PTree : PGtkTree;
|
|
begin
|
|
gtk_label_get(PgtkLabel(PGtkBin(Item)^.Child),@P);
|
|
Result:=StrPas(P);
|
|
If (PGtkWidget(item)^.Parent<>Nil) then
|
|
begin
|
|
PTree:=PGtkTree(PgtkWidget(Item)^.Parent);
|
|
If (Ptree^.Level<>0) Then
|
|
Result:=AddTrailingSeparator(GetPathName(PgtkTreeItem(PTree^.Tree_Owner)))+Result
|
|
end;
|
|
end;
|
|
\end{lstlisting}
|
|
It is a simple recursive mechanism. The only issue with this
|
|
routine is that one should know that the parent of a tree item is a tree,
|
|
and that the owner of the tree (in it's \lstinline|Tree_Owner| field) is
|
|
in turn again a treeitem. The \lstinline|Level| field of a tree determines
|
|
at what level the tree is located (i.e. the number of nodes present above
|
|
the tree) and can be used to check when the algorithm should stop.
|
|
|
|
An alternate approach would have been to associate with each node some
|
|
user data, such as a string that is the full path name of the node.
|
|
|
|
With this, the tree is created and is linked to the file list, so the
|
|
user has the capability to select any directory and display it's contents;
|
|
The user can also customize the view of the file list.
|
|
|
|
However, no actions can be performed on the files. This is treated in the
|
|
next sections, where a toolbar and popup menu are used to allow the user to
|
|
do things with the shown files.
|
|
|
|
\section{Adding a popup menu}
|
|
To allow the user to do something with the displayed files, a popup menu is
|
|
addd to the file list. Adding a popup menu is not different from adding a
|
|
main menu to a form, just it will not be attached to a menu bar. The popup
|
|
menu will be hidden till the user right-clicks in the file list.
|
|
|
|
The popup menu is created in the following function:
|
|
\begin{lstlisting}{}
|
|
Function NewFilePopupMenu (MainWindow : PMainWindow) : PGtkMenu;
|
|
|
|
begin
|
|
result:=PGtkMenu(gtk_menu_new);
|
|
gtk_signal_connect(PGtkObject(result),'show',
|
|
TGtkSignalFunc(@PMFilesActivate),MainWindow);
|
|
With MainWindow^ do
|
|
begin
|
|
PMIFileProperties:=AddItemToMenu(Result,Accel,'_Properties','',
|
|
TgtkSignalFunc(@DoProperties),
|
|
MainWindow);
|
|
PMIFileDelete:=AddItemToMenu(Result,Accel,'_Delete','<ctrl>d',
|
|
TgtkSignalFunc(@DeleteFile),
|
|
MainWindow);
|
|
end;
|
|
end;
|
|
\end{lstlisting}
|
|
The \lstinline|AddItemToMenu| functions were developed in an earlier
|
|
articles, and have been collected in the 'menus' unit.
|
|
|
|
The 'show' handler attached to the menu is used to set the state
|
|
of some of the menu items when the menu pops up:
|
|
\begin{lstlisting}{}
|
|
Procedure PMFilesActivate(Widget : PGtkWidget; Window : PMainWindow); cdecl;
|
|
|
|
Var State : TGtkStateType;
|
|
|
|
begin
|
|
if GetFileSelectionCount(Window^.FileList)>1 then
|
|
State:=GTK_STATE_INSENSITIVE
|
|
else
|
|
State:=GTK_STATE_Normal;
|
|
gtk_widget_set_state(PgtkWidget(Window^.PMIFileProperties),State);
|
|
end;
|
|
\end{lstlisting}
|
|
When more than 1 file is selected in the file view, the properties menu item
|
|
is disabled.
|
|
|
|
The popup menu will appear if the user clicks the right button in the file
|
|
list; The necessary event handler for that (\lstinline|ShowPopup|) was
|
|
attached to the CList and discussed earlier on.
|
|
|
|
The delete menu item has the following 'click' handler:
|
|
\begin{lstlisting}{}
|
|
Procedure DeleteFile(Widget : PGtkWidget; Window : PMainWindow); cdecl;
|
|
|
|
Var i : longint;
|
|
S : TStringList;
|
|
|
|
begin
|
|
S:=TStringList.Create;
|
|
Try
|
|
GetFileSelection(Window^.FileList,S);
|
|
For I:=0 to S.Count-1 do
|
|
begin
|
|
For I:=0 to S.Count-1 do
|
|
SysUtils.DeleteFile(Window^.FDir+S[i]);
|
|
end;
|
|
Finally
|
|
If S.Count>0 then
|
|
RefreshFileView(Window);
|
|
S.Free;
|
|
end;
|
|
end;
|
|
\end{lstlisting}
|
|
The routine simply retrieves the selection list and deletes all files
|
|
present in it; After that the file view is refreshed.
|
|
|
|
The properties popup menu action will be treated later on.
|
|
|
|
\section{Adding a toolbar}
|
|
The toolbar in the file explorer application will contain 2 buttons with
|
|
a pixmap on them; the pixmap will be loaded from data compiled into the
|
|
binary. The actions performed by the toolbar buttons will be the same as
|
|
the actions in the popup menu: show a file's properties and delete the file.
|
|
|
|
The creation of the toolbar for the file explorer program is done in the
|
|
following function:
|
|
\begin{lstlisting}{}
|
|
Function NewToolbar (MainWindow : PMainWindow) : PGtkToolbar;
|
|
|
|
begin
|
|
Result:=pGtkToolBar(gtk_toolbar_new(GTK_ORIENTATION_HORIZONTAL,
|
|
GTK_TOOLBAR_ICONS));
|
|
gtk_toolbar_append_item(result,
|
|
Nil,
|
|
'File Properties',
|
|
nil,
|
|
CreateWidgetFromXPm(PgtkWidget(MainWindow^.Window),
|
|
@PropertiesXPM),
|
|
TgtkSignalFunc(@DoProperties),
|
|
MainWindow);
|
|
gtk_toolbar_append_item(result,
|
|
Nil,
|
|
'Delete File',
|
|
Nil,
|
|
CreateWidgetFromXPm(PgtkWidget(MainWindow^.Window),
|
|
@DeleteXPM),
|
|
TgtkSignalFunc(@DeleteFile),
|
|
MainWindow);
|
|
end;
|
|
\end{lstlisting}
|
|
The \lstinline|gtk_toolbar_new| function creates a new toolbar. The first
|
|
argument to this call specifies the orientation for the toolbar. Possible
|
|
values for the orientation are:
|
|
\begin{description}
|
|
\item[GTK\_ORIENTATION\_HORIZONTAL] The toolbar is filled horizontally;
|
|
\item[GTK\_ORIENTATION\_VERTICAL] The toolbar is filled vertically;
|
|
\end{description}
|
|
The second argument determines the style of the toolbar; it can have the
|
|
following values:
|
|
\begin{description}
|
|
\item[GTK\_TOOLBAR\_TEXT] Toolbuttons just show a text.
|
|
\item[GTK\_TOOLBAR\_ICONS] Toolbuttons just show a pixmap.
|
|
\item[GTK\_TOOLBAR\_BOTH] toolbuttons show both a pixmap and text.
|
|
\end{description}
|
|
The style determines what widgets will be placed on new toolbuttons that
|
|
are added with the \lstinline|gtk_toolbar_append_item| or
|
|
\lstinline|gtk_toolbar_prepend_item| calls. If buttons are added to the
|
|
toolbar manually, the style has no effect.
|
|
|
|
The \lstinline|gtk_toolbar_append_item| call adds a new toolbar button
|
|
to the end of a toolbar. The \lstinline|gtk_toolbar_prepend_item| item
|
|
inserts a new button at the beginning of the toolbar. Both accept the
|
|
following arguments:
|
|
\begin{enumerate}
|
|
\item a pointer to the toolbar to which the item should be added.
|
|
\item a zero-terminated string with the text to be shown on the button.
|
|
\item a zero-terminated string with the tooltip text (the hint) for the button.
|
|
\item a zero terminated private tooltip text for the button.
|
|
\item an icon wiget, usually a GtkPixmap.
|
|
\item A callback function of type \lstinline|TGtkSignalFunc| that will be
|
|
executed when the user clicks the button.
|
|
\item Callback data pointer which will be passed to the callback.
|
|
\end{enumerate}
|
|
A toolbutton can also be inserted at a certain position with the
|
|
\lstinline|gtk_toolbar_insert_item| call. It accepts an additional (last)
|
|
argument, the position at which to insert the toolbutton.
|
|
|
|
For the toolbar of the file explorer program, the buttons contain no text
|
|
(since the \lstinline|GTK_TOOLBAR_ICONS| style was chosen for the toolbar)
|
|
they do contain an icon, a pixmap widget.
|
|
|
|
The pixmap widget is created with the following function:
|
|
\begin{lstlisting}{}
|
|
function CreateWidgetFromXPM (Window : PGtkWidget;
|
|
Data : PPChar) : PGtkWidget;
|
|
|
|
Var
|
|
mask : PGdkBitmap;
|
|
pixmap : PGdkPixMap;
|
|
|
|
begin
|
|
pixmap:=gdk_pixmap_create_from_xpm_d(window^.window,@mask,nil,ppgchar(Data));
|
|
Result:=gtk_pixmap_new(Pixmap,Mask);
|
|
gtk_widget_show(Result);
|
|
end;
|
|
\end{lstlisting}
|
|
This function accepts 2 arguments: A GTK window, and a pointer to an array
|
|
or zero-terminated strings which describe the pixmap. With these it creates
|
|
a gdk pixmap object with the \lstinline|gdk_pixmap_create_from_xpm_d| call.
|
|
this function expects the following arguments:
|
|
\begin{enumerate}
|
|
\item A pointer to a GDK window object. In the above, the GDK window of the
|
|
main window widget is used. This explains why the \lstinline|gtk_widget_realize|
|
|
call was made when creating the main window: When the widget is realized, a
|
|
window is allocated to it. If the main window widget was not realized, then
|
|
it's gdk window would be nil.
|
|
\item The address of a \lstinline|PGdkBitmap| which will be used to store
|
|
the mask of the created pixmap. The mask determines the transparent items
|
|
in the bitmap, and can be used when creating a pixmap widget. This may be
|
|
nil.
|
|
\item A pointer to a color that should be considered the transparent
|
|
color. This may be nil, in which case a default color is used.
|
|
\item A pointer to a XPM pixmap structure.
|
|
\end{enumerate}
|
|
After the GDK pixmap and the mask were created, a pixmap widget is created
|
|
from the GDK bitmap, and the widget is shown.
|
|
|
|
The pixmap data is in XPM format. The XPM format is an array of
|
|
zero-terminated strings which are organized as follows:
|
|
\begin{enumerate}
|
|
\item A string describing the pixmap dimensions and the number of colors.
|
|
The string is of the form
|
|
\begin{verbatim}
|
|
'width height #colors chars/color',
|
|
\end{verbatim}
|
|
So the string
|
|
\begin{verbatim}
|
|
'16 16 4 1'
|
|
\end{verbatim}
|
|
means a 16x16 bitmap, using 4 colors, described by 1 character per color.
|
|
\item A series of strings that describe the color. the number of strings
|
|
should equal the count specified in the first string. The color descriptions
|
|
should have the following form:
|
|
\begin{verbatim}
|
|
'X c #YYYYYY'
|
|
\end{verbatim}
|
|
here 'X' must be replaced by N characters, where N is the number of
|
|
characters per color that was specified in the first string. The YYYYYY
|
|
is a RGB color value, in hex format. Each red,green or blue value must
|
|
contain 2 or 4 characters. The string '\#FF0000' would describe red, just as
|
|
'\#FFFF00000000' would describe red.
|
|
|
|
Instead of a rgb value, 'None' can be specified to indicate a transparent
|
|
color.
|
|
Some examples of valid colors would be:
|
|
\begin{verbatim}
|
|
'. c #000000', { Black }
|
|
'# c #000080', { Dark Blue }
|
|
'a c None', { Transparent }
|
|
'b c #f8fcf8', { greyish }
|
|
\end{verbatim}
|
|
\item A series of strings of characters, each string describes one line of
|
|
the pixmap and is composed of the color characters described in the color
|
|
section. Each line has the same length, namely the width of the image
|
|
multiplied with the number of characters per color. Obviously, there
|
|
should be as many strings as the height of the pixmap.
|
|
\end{enumerate}
|
|
The \file{fxbitmaps} unit contains 2 such bitmaps; comments have been added.
|
|
|
|
After the toolbar has been added, the main form is finished. The
|
|
form in action is shown in figure \ref{fig:mainwin}.
|
|
\begin{figure}[ht]
|
|
\caption{The main window in action.}\label{fig:mainwin}
|
|
\epsfig{file=gtk4ex/mainwin.png,width=\textwidth}
|
|
\end{figure}
|
|
|
|
The toolbar contains a button to show the properties dialog. This dialog
|
|
will show the various properties of a file, and is discussed in the next
|
|
section.
|
|
|
|
\section{Adding some dialogs}
|
|
Adding some dialogs to the file explorer program is not so difficult.
|
|
Three are created, an about dialog, a file properties dialog, and a dialog
|
|
that allows to enter a file mask which will then be applied to the file
|
|
view. All three dialogs will be based on the standard GTK dialog.
|
|
|
|
Adding a dialog that shows the properties of a file is quite easy.
|
|
The standard GTK dialog widget contains 3 widgets; a vertical box
|
|
(\lstinline|vbox|) which can be used to drop widgets in, a separator
|
|
and a horizontal box (\lstinline|action_area|), which can be used to
|
|
put buttons (such as an 'OK' button) in.
|
|
|
|
The file properties dialog consists mainly of a table packed with labels and
|
|
some checkboxes. It is created in the following function:
|
|
\begin{lstlisting}{}
|
|
Type
|
|
TFilePropertiesDialog = Record
|
|
Window : PgtkDialog;
|
|
Table : PGtkTable;
|
|
OkButton : PGtkButton;
|
|
Labels : Array[0..1,0..NrTableLines] of PGtkLabel;
|
|
CheckBoxes : Array[CheckBoxLineStart..NrTableLines] of PgtkCheckButton;
|
|
end;
|
|
PFilePropertiesDialog = ^TFilePropertiesDialog;
|
|
|
|
Function NewFilePropertiesDialog(FileName : String) : PFilePropertiesDialog;
|
|
Const
|
|
CheckAttrs : Array [CheckBoxLineStart..NrTableLines] of Integer
|
|
= (faReadOnly,faArchive,faHidden,faSysFile);
|
|
|
|
Var
|
|
Info : TSearchRec;
|
|
I : Longint;
|
|
|
|
begin
|
|
Result:=New(PFilePropertiesDialog);
|
|
With Result^ do
|
|
begin
|
|
Window:=PgtkDialog(gtk_dialog_new);
|
|
gtk_window_set_title(PgtkWindow(Window),SPropsTitle);
|
|
gtk_window_set_modal(PgtkWindow(Window),True);
|
|
gtk_window_set_policy(PgtkWindow(Window),0,0,0);
|
|
gtk_window_set_position(PGtkWindow(Window),GTK_WIN_POS_CENTER);
|
|
OkButton:=PGtkButton(gtk_button_new_with_label(SOK));
|
|
gtk_box_pack_start(PgtkBox(Window^.action_area),PGtkWidget(Okbutton),False,False,5);
|
|
gtk_window_set_focus(PGtkWindow(Window),PGtkWidget(OkButton));
|
|
gtk_widget_show(PGtkWidget(OkButton));
|
|
\end{lstlisting}
|
|
The above are standard things: The dialog window title is set, the dialog is
|
|
made modal, the resizing of the window is prohibited with the
|
|
\lstinline|gtk_window_set_policy| call. Then the window is told that it
|
|
should position itself in the center of the screen with the
|
|
\lstinline|gtk_window_set_position| call. The position specifier can be one
|
|
of the following:
|
|
\begin{description}
|
|
\item[GTK\_WIN\_POS\_NONE] The window manager will decide where the window
|
|
goes.
|
|
\item[GTK\_WIN\_POS\_CENTER] The window is placed at the center of the
|
|
screen.
|
|
\item[GTK\_WIN\_POS\_MOUSE] The window is placed where the mouse cursor is.
|
|
\end{description}
|
|
After the window properties have been set, an OK button is placed in the
|
|
action area, and it gets the focus.
|
|
|
|
Next, a table is created with \lstinline|NrTableLines+1| rows and 2 columns,
|
|
and put in the vbox area:
|
|
\begin{lstlisting}{}
|
|
Table:=PgtkTable(gtk_table_new(NrTableLines+1,2,TRUE));
|
|
gtk_box_pack_start(PGtkBox(Window^.vbox),PGtkWidget(Table),True,True,10);
|
|
\end{lstlisting}
|
|
Then the table is filled with labels that describe the various properties;
|
|
the left column contains labels that simplu
|
|
\begin{lstlisting}{}
|
|
For I:=0 to NrTableLines do
|
|
begin
|
|
Labels[0,i]:=PGtkLabel(gtk_label_new(LabelTexts[i]));
|
|
gtk_label_set_justify(Labels[0,I],GTK_JUSTIFY_RIGHT);
|
|
gtk_table_attach_defaults(Table,PgtkWidget(Labels[0,I]),0,1,I,I+1);
|
|
end;
|
|
For I:=0 to CheckboxLineStart-1 do
|
|
begin
|
|
Labels[1,i]:=PGtkLabel(gtk_label_new(''));
|
|
gtk_label_set_justify(Labels[1,I],GTK_JUSTIFY_LEFT);
|
|
gtk_table_attach_defaults(Table,PgtkWidget(Labels[1,I]),1,2,I,I+1);
|
|
end;
|
|
\end{lstlisting}
|
|
The file attributes will be represented with checkboxes:
|
|
\begin{lstlisting}{}
|
|
For I:=CheckboxLineStart to NrTableLines do
|
|
begin
|
|
checkBoxes[i]:=PgtkCheckButton(gtk_check_button_new_with_label(CheckBoxTexts[I]));
|
|
gtk_widget_set_state(PGtKWidget(CheckBoxes[i]),GTK_STATE_INSENSITIVE);
|
|
gtk_table_attach_defaults(Table,PgtkWidget(CheckBoxes[i]),1,2,I,I+1);
|
|
end;
|
|
\end{lstlisting}
|
|
The checkboxes are made inactive, so the user cannot change them.
|
|
|
|
After all labels and checkboxes are put in place, the file information
|
|
is put into various places:
|
|
\begin{lstlisting}{}
|
|
gtk_label_set_text(Labels[1,0],PChar(ExtractFileName(FileName)));
|
|
gtk_label_set_text(Labels[1,1],PChar(ExtractFilePath(FileName)));
|
|
gtk_label_set_text(Labels[1,2],PChar(ExtractFileExt(FileName)+SFile));
|
|
If FindFirst(FileName,faAnyFile,Info)=0 Then
|
|
begin
|
|
gtk_label_set_text(Labels[1,3],PChar(FileSizeToString(Info.Size)));
|
|
gtk_label_set_text(Labels[1,4],PChar(DateTimeToStr(FileDateToDateTime(Info.Time))));
|
|
For I:=CheckboxLineStart to NrTableLines do
|
|
If (CheckAttrs[i] and Info.Attr)=CheckAttrs[i] then
|
|
gtk_toggle_button_set_active(PgtkToggleButton(CheckBoxes[I]),True);
|
|
FindClose(Info);
|
|
end;
|
|
\end{lstlisting}
|
|
Finally, the 'destroy' callback for the window is set, and the OK button's
|
|
'click' signal is attached to the destroy method of the window widget:
|
|
\begin{lstlisting}{}
|
|
gtk_signal_connect(PGtkObject(Window),'destroy',
|
|
TGTKSignalFunc(@DestroyPropDialog),Result);
|
|
gtk_signal_connect_object(PgtkObject(OKButton),'clicked',
|
|
GTK_SIGNAL_FUNC(@gtk_widget_destroy),
|
|
PGTKOBJECT(Window));
|
|
end;
|
|
end;
|
|
\end{lstlisting}
|
|
Showing the properties dialog is simple:
|
|
\begin{lstlisting}{}
|
|
Procedure ShowFilePropertiesDialog(Dialog : PFilePropertiesDialog);
|
|
|
|
begin
|
|
gtk_widget_show_all(PgtkWidget(Dialog^.Window));
|
|
end;
|
|
\end{lstlisting}
|
|
|
|
The result of all this is shown in figure \ref{fig:fileprops}.
|
|
\begin{figure}[ht]
|
|
\begin{center}
|
|
\caption{The file properties dialog.}\label{fig:fileprops}
|
|
\epsfig{file=gtk4ex/fileprops.png,width=8cm}
|
|
\end{center}
|
|
\end{figure}
|
|
|
|
The handling of the mask form is a little bit more complicated than the
|
|
properties dialog, since the mask form should return some information
|
|
to the main form.
|
|
|
|
The creation of the mask form is again a standard matter, and the reader
|
|
is referred to the code on the CD-ROM to see how it is handled. The
|
|
only thing worth noting is the handling of the click on the 'OK' button that
|
|
appears on the form.
|
|
\begin{lstlisting}{}
|
|
gtk_signal_connect(PgtkObject(OKButton),'clicked',
|
|
TGtkSignalFunc(@ApplyMask),Result);
|
|
gtk_signal_connect_object(PgtkObject(OKButton),'clicked',
|
|
GTK_SIGNAL_FUNC(@gtk_widget_destroy),
|
|
PGTKOBJECT(Window));
|
|
\end{lstlisting}
|
|
Two handlers are added to the 'clicked' signal of the 'OK' button.
|
|
The first one is pointed to a function that will apply the mask, and the
|
|
second one is redirected to the destroy method of the dialog window wigdet.
|
|
\begin{lstlisting}{}
|
|
Procedure ApplyMask(Widget : PGtkWidget; Window : PMaskForm);cdecl;
|
|
|
|
begin
|
|
With Window^ do
|
|
begin
|
|
Mask:=StrPas(gtk_entry_get_text(EMask));
|
|
If (CallBack<>Nil) then
|
|
CallBack(Mask,CallBackData);
|
|
end;
|
|
end;
|
|
\end{lstlisting}
|
|
The \lstinline|TMaskForm| record that contains fields for all widgets on
|
|
the mask entry form also contains 2 fields that allow the OK button to notify
|
|
the calling program of the new mask:
|
|
\begin{lstlisting}{}
|
|
TMaskCallBack = Procedure (Mask : String; Data : Pointer);
|
|
TMaskForm = Record
|
|
{ ... widget fields ... }
|
|
Mask : ShortString;
|
|
CallBack : TMaskCallBack;
|
|
CallBackData : Pointer;
|
|
end;
|
|
PMaskForm = ^TMaskForm;
|
|
\end{lstlisting}
|
|
If the callback field is set, then the \lstinline|ApplyMask| function will call
|
|
it and pass it the new mask and some arbitrary pointer.
|
|
|
|
The main form contains a 'file mask' menu item, which has the following
|
|
'click' handler:
|
|
\begin{lstlisting}{}
|
|
procedure DoMask(Widget : PGtkWidget ; MainForm : PMainWindow ); cdecl;
|
|
|
|
Var
|
|
S : AnsiString;
|
|
|
|
begin
|
|
With NewMaskForm^ do
|
|
begin
|
|
S:=MainForm^.FMask;
|
|
gtk_entry_set_text(EMask,PChar(S));
|
|
CallBack:=@ApplyMask;
|
|
CallBackData:=MainForm;
|
|
gtk_widget_show_all(PgtkWidget(Window));
|
|
end;
|
|
end;
|
|
\end{lstlisting}
|
|
When the user clicks the 'file mask' menu item, A mask entry form is created.
|
|
The current file mask is filled in the entry widget (\lstinline|EMask|).
|
|
The callback is set, and the callbackdata is set to the pointer to the main
|
|
window record. The callback that is executed when the user clicks the OK
|
|
button on the mask form is the following:
|
|
\begin{lstlisting}{}
|
|
Procedure ApplyMask(Mask : String; Data : Pointer);
|
|
|
|
begin
|
|
PMainWindow(data)^.FMask:=Mask;
|
|
RefreshFileView(PMainWindow(Data));
|
|
end;
|
|
\end{lstlisting}
|
|
|
|
The reason that this system of callbacks is needed is that the
|
|
\lstinline|gtk_widget_show_all| immediatly returns when the mask entry form is
|
|
shown. Even though the mask entry form dialog is a modal dialog (i.e. it alone will
|
|
respond to mouse clicks and key presses) the call returns immediatly,
|
|
there is no counterpart for the Delphi \lstinline|ShowModal| function.
|
|
|
|
When the \lstinline|gtk_widget_show_all| returns, the mask entry form is still on
|
|
the screen, so the changes made in the mask form must be communicated
|
|
back to the main form by means of a callback which is executed when
|
|
the mask entry form is closed.
|
|
|
|
The mask form in action is shown in figure \ref{fig:filemask}.
|
|
\begin{figure}[ht]
|
|
\begin{center}
|
|
\caption{The file properties dialog.}\label{fig:filemask}
|
|
\epsfig{file=gtk4ex/filemask.png,width=8cm}
|
|
\end{center}
|
|
\end{figure}
|
|
|
|
\section{Finishing the application}
|
|
In several places in this article, a reference was made to the main menu.
|
|
The main menu is created in the \lstinline|NewMainMenu| function; since
|
|
menus were discussed extensively in the previous article on programming GTK,
|
|
the code will not be presented here. The various calls developed in the
|
|
previous article have been collected in the \file{menus} unit. One
|
|
additional call was added which adds a check menuitem to a menu; the call is
|
|
similar to the regular menu item calls, and will not be discussed here.
|
|
|
|
The application is built in such a way that it can easily be extended.
|
|
Only 2 file actions have been implemented, but many more can be made.
|
|
Missing functionality includes:
|
|
\begin{itemize}
|
|
\item Renaming of files. The CList allows to put an arbitrary widget into
|
|
a cell; this functionality could be used to allow the user to change the
|
|
filename by simply editing it.
|
|
\item Moving and copying of files, using drag and drop.
|
|
\item Duplicating the main window, or spawning a new window.
|
|
\item Opening a file in another application.
|
|
\item Improve the look of the file properties form.
|
|
\item On Windows, support for showing different drives should be added.
|
|
\end{itemize}
|
|
And without doubt, many more can be found.
|
|
|
|
\end{document} |