Step 2: Browsing Files
In Step 1: Building the Browser Window you saw how to create the graphic objects for displaying a hierarchical list of files. This second step shows how to define and instantiate the file browser, that is, the object used to fill the graphic objects (the tree and the sheet). The class used to browse the hard disk is FileViewer. It is declared in the file viewer.h and defined in the file viewer.cpp.
This step shows you how to perform the following tasks:
Note: The classes IlPathName and IlString seen in the code for this step are used to make the code easier to read.
Creating the File Browser
The FileViewer class is long and complex. This section explains its main parts:
The Constructor
The FileViewer constructor requires an IlvTreeGadget and an IlvSheet as its parameters. Both objects are initialized by the initObjects member function:
FileViewer::FileViewer(IlvTreeGadget* tree, IlvSheet* sheet)
: _tree(tree), _sheet(sheet)
{
// Initialize the gadgets.
initObjects();
}
Initializing the Tree
The tree object is initialized with the initTree member function:
void
FileViewer::initTree()
{
// Set up the tree gadget.
_tree->removeAllItems(IlvFalse);
_tree->removeCallback(IlvTreeGadget::ExpandCallbackType(),
TreeItemExpanded);
_tree->addCallback(IlvTreeGadget::ExpandCallbackType(),
TreeItemExpanded,
this);
_tree->removeCallback(IlvTreeGadget::SelectCallbackType(),
TreeItemSelected);
_tree->addCallback(IlvTreeGadget::SelectCallbackType(),
TreeItemSelected,
this);
}
This member function removes all the items in the tree and sets two callbacks—a selection callback that updates the sheet whenever a new folder is selected in the tree, and a callback that is triggered each time a tree item is expanded. This second callback prevents the FileViewer object from loading the entire file hierarchy at once, which would slow down the operation. Folders are loaded only when expanded.
Note: Each callback is removed prior to being added to avoid registering the same callback twice.
For more information, see “Callbacks” in Graphic Objects.
Initializing the Sheet
The sheet is initialized with the initSheet member function:
void
FileViewer::initSheet()
{
// Set up the sheet.
_sheet->scrollBarShowAsNeeded(IlvTrue, IlvFalse);
_sheet->hideScrollBar(IlvHorizontal, IlvFalse);
_sheet->adjustLast(IlvTrue);
_sheet->reinitialize(3, 1);
_sheet->setNbFixedColumn(0);
_sheet->scrollToColumn(0);
_sheet->setExclusive(IlvTrue);
_sheet->allowEdit(IlvFalse);
 
// Create the header.
_sheet->set(0, 0, new IlvLabelMatrixItem("Name"));
_sheet->setItemAlignment(0, 0, IlvLeft);
_sheet->setItemRelief(0, 0, IlvTrue);
_sheet->setItemSensitive(0, 0, IlvFalse);
_sheet->setItemGrayed(0, 0, IlvFalse);
_sheet->set(1, 0, new IlvLabelMatrixItem("Size"));
_sheet->setItemRelief(1, 0, IlvTrue);
_sheet->setItemSensitive(1, 0, IlvFalse);
_sheet->setItemGrayed(1, 0, IlvFalse);
_sheet->set(2, 0, new IlvLabelMatrixItem("Type"));
_sheet->setItemAlignment(2, 0, IlvLeft);
_sheet->setItemRelief(2, 0, IlvTrue);
_sheet->setItemSensitive(2, 0, IlvFalse);
_sheet->setItemGrayed(2, 0, IlvFalse);
}
This member function modifies the sheet so that it has three columns and only one row in which the sheet header will be displayed. Column 1 will contain file names, column 2 will contain the size of the files, and column 3 will provide information about the file type, as shown in the following figure:
Note: The items making up the header are set to insensitive, meaning that the user will not be able to select them.
Scanning Directories
The fillTree member function fills the tree with data once it is initialized. It reads the contents of a given directory and creates the corresponding items in the tree:
void
FileViewer::fillTree(IlvPathName& path, IlvTreeGadgetItem* parent)
{
// Read the 'path' directory and insert each sub-folder as a child of
// the item 'parent'.
if (path.openDir()) {
IlvPathName file;
while (path.readDir(file)) {
if (file.isDirectory() &&
file.getDirectory(IlvFalse) != IlvString(".") &&
file.getDirectory(IlvFalse) != IlvString("..")) {
IlvTreeGadgetItem* item =
(IlvTreeGadgetItem*)createFileItem(file, _tree);
item->setUnknownChildCount(IlvTrue);
// Insert the directory 'file' into parent.
parent->insertChild(item);
// Compute the absolute path name.
IlvPathName* absPathName=new IlvPathName(path);
absPathName->merge(file);
item->setclientData(absPathName)
}
}
path.closeDir();
// Sort the added items.
_tree->sort(parent, 1);
}
}
The FileViewer::createFileItem member function creates each of the tree items. These are then added to their parent item, that is, to the folder being read. The added items are then sorted by the sort member function.
The content of createFileItem is described in Creating the Gadget Items.
Notes:  
1. The member function setUnknownChildCount is called for each added item to ensure that the Expand callback is invoked. For more information, see “Changing the Characteristics of an Item” in Using Common Gadgets.
2. The client data of an item is used to store the path object that references the directory that the item points to. This object is a cache that avoids recomputing the absolute path of the item each time.
The fillTree method fills only one node of the tree. The whole tree is actually built by the Expand callback:
static void
TreeItemExpanded(IlvGraphic* g, IlvAny arg)
{
IlvTreeGadgetItem* item = ((IlvTreeGadget*)g)->getCallbackItem();
if (!item->hasChildren()) {
FileViewer* viewer = (FileViewer*)arg;
IlvPathName* path = (IlvPathName*)item->getClientData();
viewer->updateTree(*path, item);
}
}
This callback is invoked each time the user tries to expand a collapsed folder. The arg parameter is a pointer to the FileViewer object, as specified when the callback was set. The path that was stored as the item client data is retrieved and used to update the tree. Since you want to update the tree only if the folder has never been open, ensure that the item being expanded has no children before calling the updateTree method:
void
FileViewer::updateTree(IlvPathName& path, IlvTreeGadgetItem* item)
{
_tree->initReDrawItems();
// Reset the tree, if needed.
if (!item)
initTree();
// Fill it using the 'path' directory.
fillTree(path, item? item : _tree->getRoot());
// Finally, redraw.
_tree->reDrawItems();
}
This member function calls the fillTree member function and performs additional operations such as cleaning the tree and handling smart redrawing. See “Redrawing Gadget Items” in Gadget Items.
Updating the Sheet
The list of files represented by an IlvSheet object on the right-hand side of the window must be updated each time the item selected in the tree changes. Updating is performed by the selection callback of the IlvTreeGadget class.
static void
TreeItemSelected(IlvGraphic* g, IlvAny arg)
{
// Retrieve the item that has triggered the callback.
IlvTreeGadgetItem* item = ((IlvTreeGadget*)g)->getCallbackItem();
// In the case of a selection (this callback is also called when an item
// is deselected) update the sheet.
if (item->isSelected()) {
// Retrieve the path of the item.
IlvPathName* pathname = (IlvPathName*)item->getClientData();
// Retrieve the file viewer instance.
FileViewer* viewer = (FileViewer*)arg;
// Update the sheet.
viewer->updateSheet(*pathname);
}
}
This callback is invoked each time a new item is selected in the tree. Once the item that triggered the callback is retrieved by the member function IlvTreeGadget::getCallbackItem, it is necessary to check whether the item is selected (since the tree selection callback is also invoked when an item is deselected). Then, the path stored in the item client data is passed to the updateSheet member function:
void
FileViewer::updateSheet(IlvPathName& path)
{
_sheet->initReDrawItems();
// Reset the sheet.
initSheet();
// Read all the files contained in 'path'.
if (path.openDir()) {
IlvPathName file;
while (path.readDir(file)) {
if (!file.isDirectory() ||
(file.getDirectory(IlvFalse) != IlvString(".") &&
file.getDirectory(IlvFalse) != IlvString(".."))) {
if (!file.isDirectory())
file.setDirectory(path);
// Add the file to the sheet.
addFile(file);
}
}
path.closeDir();
}
// Recompute the column sizes.
_sheet->fitWidthToSize();
// Invalidate the whole sheet.
_sheet->getHolder()->invalidateRegion(_sheet);
// Finally, redraw.
_sheet->reDrawItems();
}
This member function adds all the files specified in the directory path to the sheet using the addFile member function. It also manages the sheet redrawing:
void
FileViewer::addFile(const IlvPathName& file)
{
// Create the gadget item that will be inserted into the sheet.
IlvGadgetItem* item = createFileItem(file, _sheet);
// Encapsulate it with a matrix item.
IlvAbstractMatrixItem* mitem = new IlvGadgetItemMatrixItem(item);
// Add a new row in the matrix .
_sheet->insertRow((IlvUShort) -1);
IlvUShort row = (IlvUShort)(_sheet->rows() - 1);
 
// Set the item in the first column.
_sheet->set(0, row, mitem);
_sheet->resizeRow((IlvUShort)(row + 1), item->getHeight() + 1);
_sheet->setItemAlignment(0, row, _sheet->getItemAlignment(0, 0));
// File Size in the second column.
_sheet->setItemSensitive(1, row, IlvFalse);
_sheet->setItemGrayed(1, row, IlvFalse);
_sheet->setItemAlignment(1, row, _sheet->getItemAlignment(1, 0));
ifstream ifile(file.getString(), IlvBinaryInputStreamMode);
if (!(!ifile)) {
ifile.seekg(0, ios::end);
streampos length = ifile.tellg();
mitem = new IlvIntMatrixItem(length);
_sheet->set(1, row, mitem);
}
// File Type in the third column.
mitem = new IlvLabelMatrixItem(file.isDirectory()
? "File Folder"
: "File");
_sheet->setItemSensitive(2, row, IlvFalse);
_sheet->setItemGrayed(2, row, IlvFalse);
_sheet->setItemAlignment(2, row, _sheet->getItemAlignment(2, 0));
_sheet->set(2, row, mitem);
}
This member function creates the item corresponding to the file parameter provided as its argument and adds it to the first column of the sheet. Then, it retrieves the file size and puts it in the second column as an IlvIntMatrixItem object. Finally, it puts the file type in the third column as an IlvLabelMatrixItem object.
Both the second and third column items are set to insensitive, meaning that the user will not be able to select them.
Note: The file names in the first column of the sheet are created with the same method as the one used for building the tree.
Creating the Gadget Items
The member function FileViewer::createFileItem creates the items:
IlvGadgetItem*
FileViewer::createFileItem(const IlvPathName& file,
const IlvGadgetItemHolder* holder) const
{
// Compute the item label.
IlvString filename = file.isDirectory()
? file.getString()
: file.getBaseName();
// If file is a directory, remove the trailing '/'.
if (file.isDirectory())
filename.remove(filename.getLength() - 1);
return
holder->createItem(filename,
0,
getBitmap(file, IlvGadgetItem::BitmapSymbol()),
getBitmap(file,
IlvGadgetItem::SelectedBitmapSymbol()));
}
A gadget item can include both a picture and a label. For a detailed description of gadget items, see Gadget Items. The item label is computed from the file parameter. Items are assigned two bitmaps—one that represents them when they are selected and one that represents them when they are not selected.
Then, the item is created using the factory member function IlvGadgetItemHolder::createItem, which is called on the holder instance provided as its parameter. See “Creating Gadget Items” in Gadget Items.
In the section Scanning Directories, the items of both the tree and the sheet are created by calling this method. This is because the classes IlvTreeGadget and IlvSheet both inherit from the IlvGadgetItemHolder class. Calling the createFileItem member function with an instance of the IlvTreeGadget class as its holder parameter returns an instance of IlvTreeGadgetItem, whereas calling it with an instance of IlvSheet returns an instance of the IlvGadgetItem class.
The member function IlvGadgetItemHolder::createItem takes two bitmaps as its third and fourth parameters. This makes it easy to create an item with predefined bitmaps.
However, the call to createItem could have been replaced by the following code, which shows how to assign a bitmap to a gadget item:
IlvGadgetItem* item = holder->createItem(filename);
item->setBitmap(IlvGadgetItem::BitmapSymbol(),
getBitmap(file, IlvGadgetItem::BitmapSymbol()));
item->setBitmap(IlvGadgetItem::SelectedBitmapSymbol(),
getBitmap(file, IlvGadgetItem::SelectedBitmapSymbol()));
return item;
The getBitmap method used here returns the bitmap corresponding to the pair file type/item state. Its code is very simple, as can be seen here:
IlvBitmap*
FileViewer::getBitmap(const IlvPathName& file,
const IlvSymbol* state) const
{
if (state == IlvGadgetItem::BitmapSymbol())
return getBitmap(file.isDirectory()
? folderBm
: fileBm);
else
return getBitmap(file.isDirectory()
? sfolderBm
: sfileBm);
}
folderBm, fileBm, sfolderBm, and sfileBm are defined symbols, where:
*folderBm is the name of the bitmap used for folders.
*fileBm is the name of the bitmap used for nonselected files.
*sfolderBm is the name of the bitmap used for selected folders.
*sfileBm is the name of the bitmap used for selected files.
Instantiating the File Viewer
The FileViewer class is now complete. To instantiate it in the application, you must add a call to the member function FileViewerApplication::configureApplication in FileViewerApplication::makePanels, which was defined in the section Creating the Main Window.
void
FileViewerApplication::makePanels()
{
// Initialize the main window.
initMainWindow();
// Initialize the application.
configureApplication();
// Show it.
getMainWindow()->show();
}
The FileViewerApplication::configureApplication member function instantiates and initializes the FileViewer class:
void
FileViewerApplication::configureApplication()
{
FileViewer* viewer = createFileViewer(getMainWindow());
FileViewerApplication::SetFileViewer(getMainWindow(), viewer);
viewer->init(IlvPathName("/"));
}
Note: Calling FileViewerApplication::SetFileViewer connects the main window to the viewer. You can retrieve the file viewer connected to a specific window with FileViewerApplication::GetFileViewer.
The factory method createFileViewer returns a new instance of the FileViewer class:
FileViewer*
FileViewerApplication::createFileViewer(FileViewerWindow* window) const
{
return new FileViewer(window->getDirectoryHierarchy(),
window->getFileList());
}
Previewing the Application
This is the end of Step 2. Your application window should look like this:
The ViewFile Application After Step2
Published date: 05/24/2022
Last modified date: 02/24/2022