/* Copyright (C) 2009 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
/////////////////////////////////////////////////////////////////////////////
// Name: wxVirtualDirTreeCtrl.cpp
/////////////////////////////////////////////////////////////////////////////
#include "precompiled.h"
//#ifdef __GNUG__
// #pragma implementation "wxVirtualDirTreeCtrl.cpp"
//#endif
// For compilers that support precompilation, includes "wx/wx.h".
#include "wx/wxprec.h"
#ifdef __BORLANDC__
#pragma hdrstop
#endif
#include
#include
#include "virtualdirtreectrl.h"
// default images
#include "folder.xpm"
#include "file.xpm"
#include "root.xpm"
// WDR: class implementations
//----------------------------------------------------------------------------
// wxVirtualDirTreeCtrl
//----------------------------------------------------------------------------
// WDR: event table for wxVirtualDirTreeCtrl
BEGIN_EVENT_TABLE(wxVirtualDirTreeCtrl, wxTreeCtrl)
EVT_TREE_ITEM_EXPANDING(-1, wxVirtualDirTreeCtrl::OnExpanding)
END_EVENT_TABLE()
wxVirtualDirTreeCtrl::wxVirtualDirTreeCtrl(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, const wxValidator& validator, const wxString& name)
: wxTreeCtrl(parent, id, pos, size, style, validator, name)
, _flags(wxVDTC_DEFAULT)
{
// create an icon list for the tree ctrl
_iconList = new wxImageList(16,16);
// reset to default extension list
ResetExtensions();
}
wxVirtualDirTreeCtrl::~wxVirtualDirTreeCtrl()
{
// first delete all VdtcTreeItemBase items (client data)
DeleteAllItems();
// delete the icons
delete _iconList;
}
bool wxVirtualDirTreeCtrl::SetRootPath(const wxString &root, int flags)
{
bool value;
wxBusyInfo *bsy = 0;
wxLogNull log;
// set flags to adopt new behaviour
_flags = flags;
// delete all items plus root first
DeleteAllItems();
VdtcTreeItemBase *start = 0;
// now call for icons management, the virtual
// handler so the derived class can assign icons
_iconList->RemoveAll();
OnAssignIcons(*_iconList);
SetImageList(_iconList);
value = ::wxDirExists(root);
if(value)
{
// call virtual handler to notify the derived class
OnSetRootPath(root);
// create a root item
start = OnCreateTreeItem(VDTC_TI_ROOT, root);
if(start)
{
wxFileName path;
path.AssignDir(root);
// call the add callback and find out if this root
// may be added (later on)
if(OnAddRoot(*start, path))
{
// add this item to the tree, with info of the developer
wxTreeItemId id = AddRoot(start->GetCaption(), start->GetIconId(), start->GetSelectedIconId(), start);
// show a busy dialog
if(_flags & (wxVDTC_RELOAD_ALL | wxVDTC_SHOW_BUSYDLG))
bsy = new wxBusyInfo(_("Please wait, scanning directory..."), 0);
// scan directory, either the smart way or not at all
ScanFromDir(start, path, (wxVDTC_RELOAD_ALL & _flags ? -1 : VDTC_MIN_SCANDEPTH));
// expand root when allowed
if(!(_flags & wxVDTC_NO_EXPAND))
Expand(id);
}
else
delete start; // sorry not succeeded
}
}
// delete busy info if present
if(bsy)
delete bsy;
return value;
}
int wxVirtualDirTreeCtrl::ScanFromDir(VdtcTreeItemBase *item, const wxFileName &path, int level)
{
int value = 0;
wxCHECK(item, -1);
wxCHECK(item->IsDir() || item->IsRoot(), -1);
wxLogNull log;
// when we can still scan, do so
if(level == -1 || level > 0)
{
// TODO: Maybe when a reload is issued, delete all items that are no longer present
// in the tree (on disk) and check if all new items are present, else add them
// if no items, then go iterate and get everything in this branch
if(GetChildrenCount(item->GetId()) == 0)
{
VdtcTreeItemBaseArray addedItems;
// now call handler, if allowed, scan this dir
if(OnDirectoryScanBegin(path))
{
// get directories
GetDirectories(item, addedItems, path);
// get files
if(!(_flags & wxVDTC_NO_FILES))
GetFiles(item, addedItems, path);
// call handler that can do a last thing
// before sort and anything else
OnDirectoryScanEnd(addedItems, path);
// sort items
if(addedItems.GetCount() > 0 && (_flags & wxVDTC_NO_SORT) == 0)
SortItems(addedItems, 0, (int)addedItems.GetCount()-1);
AddItemsToTreeCtrl(item, addedItems);
// call handler to tell that the items are on the tree ctrl
OnAddedItems(item);
}
}
value = (int)GetChildrenCount(item->GetId());
// go through all children of this node, pick out all
// the dir classes, and descend as long as the level allows it
// NOTE: Don't use the addedItems array, because some new can
// be added or some can be deleted.
wxTreeItemIdValue cookie = 0;
VdtcTreeItemBase *b;
wxTreeItemId child = GetFirstChild(item->GetId(), cookie);
while(child.IsOk())
{
b = (VdtcTreeItemBase *)GetItemData(child);
if(b && b->IsDir())
{
wxFileName tp = path;
tp.AppendDir(b->GetName());
value += ScanFromDir(b, tp, (level == -1 ? -1 : level-1));
}
child = GetNextChild(item->GetId(), cookie);
}
}
return value;
}
void wxVirtualDirTreeCtrl::GetFiles(VdtcTreeItemBase *WXUNUSED(parent), VdtcTreeItemBaseArray &items, const wxFileName &path)
{
wxFileName fpath;
wxString fname;
VdtcTreeItemBase *item;
fpath = path;
// no nodes present yet, we should start scanning this dir
// scan files first in this directory, with all extensions in this array
for(size_t i = 0; i < _extensions.Count(); i++)
{
wxDir fdir(path.GetFullPath());
if(fdir.IsOpened())
{
bool bOk = fdir.GetFirst(&fname, _extensions[i], wxDIR_FILES | wxDIR_HIDDEN);
while(bOk)
{
// TODO: Flag for double items
item = AddFileItem(fname);
if(item)
{
// fill it in, and marshall it by the user for info
fpath.SetFullName(fname);
if(OnAddFile(*item, fpath))
items.Add(item);
else
delete item;
}
bOk = fdir.GetNext(&fname);
}
}
}
}
void wxVirtualDirTreeCtrl::GetDirectories(VdtcTreeItemBase *WXUNUSED(parent), VdtcTreeItemBaseArray &items, const wxFileName &path)
{
wxFileName fpath;
wxString fname;
VdtcTreeItemBase *item;
// no nodes present yet, we should start scanning this dir
// scan files first in this directory, with all extensions in this array
wxDir fdir(path.GetFullPath());
if(fdir.IsOpened())
{
bool bOk = fdir.GetFirst(&fname, VDTC_DIR_FILESPEC, wxDIR_DIRS | wxDIR_HIDDEN);
while(bOk)
{
// TODO: Flag for double items
item = AddDirItem(fname);
if(item)
{
// fill it in, and marshall it by the user for info
fpath = path;
fpath.AppendDir(fname);
if(OnAddDirectory(*item, fpath))
items.Add(item);
else
delete item;
}
bOk = fdir.GetNext(&fname);
}
}
}
int wxVirtualDirTreeCtrl::OnCompareItems(const wxTreeItemId& item1, const wxTreeItemId& item2)
{
// used for SortChildren, reroute to our sort routine
VdtcTreeItemBase *a = (VdtcTreeItemBase *)GetItemData(item1),
*b = (VdtcTreeItemBase *)GetItemData(item2);
if(a && b)
return OnCompareItems(a,b);
return 0;
}
int wxVirtualDirTreeCtrl::OnCompareItems(const VdtcTreeItemBase *a, const VdtcTreeItemBase *b)
{
// if dir and other is not, dir has preference
if(a->IsDir() && b->IsFile())
return -1;
else if(a->IsFile() && b->IsDir())
return 1;
// else let ascii fight it out
return a->GetCaption().CmpNoCase(b->GetCaption());
}
void wxVirtualDirTreeCtrl::SwapItem(VdtcTreeItemBaseArray &items, int a, int b)
{
VdtcTreeItemBase *t = items[b];
items[b] = items[a];
items[a] = t;
}
void wxVirtualDirTreeCtrl::SortItems(VdtcTreeItemBaseArray &items, int left, int right)
{
VdtcTreeItemBase *a, *b;
int i, last;
if(left >= right)
return;
SwapItem(items, left, (left + right)/2);
last = left;
for(i = left+1; i <= right; i++)
{
a = items[i];
b = items[left];
if(a && b)
{
if(OnCompareItems(a, b) < 0)
SwapItem(items, ++last, i);
}
}
SwapItem(items, left, last);
SortItems(items, left, last-1);
SortItems(items, last+1, right);
}
void wxVirtualDirTreeCtrl::AddItemsToTreeCtrl(VdtcTreeItemBase *item, VdtcTreeItemBaseArray &items)
{
wxCHECK2(item, return);
// now loop through all elements on this level and add them
// to the tree ctrl pointed out by 'id'
VdtcTreeItemBase *t;
wxTreeItemId id = item->GetId();
for(size_t i = 0; i < items.GetCount(); i++)
{
t = items[i];
if(t)
AppendItem(id, t->GetCaption(), t->GetIconId(), t->GetSelectedIconId(), t);
}
}
wxFileName wxVirtualDirTreeCtrl::GetRelativePath(const wxTreeItemId &id)
{
wxFileName value;
wxCHECK(id.IsOk(), value);
VdtcTreeItemBase *b = (VdtcTreeItemBase *)GetItemData(id);
wxCHECK(b, value);
AppendPathRecursively(b, value, false);
return value;
}
wxFileName wxVirtualDirTreeCtrl::GetFullPath(const wxTreeItemId &id)
{
wxFileName value;
wxCHECK(id.IsOk(), value);
VdtcTreeItemBase *b = (VdtcTreeItemBase *)GetItemData(id);
wxCHECK(b, value);
AppendPathRecursively(b, value, true);
return value;
}
wxTreeItemId wxVirtualDirTreeCtrl::ExpandToPath(const wxFileName &path)
{
wxTreeItemId value((void *)0);
wxFileName seekpath;
wxArrayString paths;
VdtcTreeItemBase *ptr;
paths = path.GetDirs();
// start in root section, and find the path sections that
// match the sequence
wxTreeItemId root = GetRootItem();
if(root.IsOk())
{
wxTreeItemId curr = root, id;
for(size_t i = 0; i < paths.GetCount(); i++)
{
// scan for name on this level of children
wxString currpath = paths[i];
bool not_found = true;
wxTreeItemIdValue cookie;
id = GetFirstChild(curr, cookie);
while(not_found && id.IsOk())
{
ptr = (VdtcTreeItemBase *)GetItemData(id);
not_found = !ptr->GetName().IsSameAs(currpath, false);
// prevent overwriting id
if(!not_found)
{
// we found the name, now to ensure there are more
// names loaded from disk, we call ScanFromDir (it will abort anywayz
// when there are items in the dir)
if(ptr->IsDir())
{
// TODO: This getfullpath might be a too high load, we can also
// walk along with the path, but that is a bit more tricky.
seekpath = GetFullPath(id);
ScanFromDir(ptr, seekpath, VDTC_MIN_SCANDEPTH);
}
curr = id;
}
else
id = GetNextChild(curr, cookie);
}
// now, if not found we break out
if(not_found)
return value;
}
// when we are here we are at the final node
Expand(curr);
value = curr;
}
return value;
}
bool wxVirtualDirTreeCtrl::IsRootNode(const wxTreeItemId &id)
{
bool value = false;
wxCHECK(id.IsOk(), value);
VdtcTreeItemBase *b = (VdtcTreeItemBase *)GetItemData(id);
if(b)
value = b->IsRoot();
return value;
}
bool wxVirtualDirTreeCtrl::IsDirNode(const wxTreeItemId &id)
{
bool value = false;
wxCHECK(id.IsOk(), value);
VdtcTreeItemBase *b = (VdtcTreeItemBase *)GetItemData(id);
if(b)
value = b->IsDir();
return value;
}
bool wxVirtualDirTreeCtrl::IsFileNode(const wxTreeItemId &id)
{
bool value = false;
wxCHECK(id.IsOk(), value);
VdtcTreeItemBase *b = (VdtcTreeItemBase *)GetItemData(id);
if(b)
value = b->IsFile();
return value;
}
/** Appends subdirs up until root. This is done by finding the root first and
going back down to the original caller. This is faster because no copying takes place */
void wxVirtualDirTreeCtrl::AppendPathRecursively(VdtcTreeItemBase *b, wxFileName &dir, bool useRoot)
{
wxCHECK2(b, return);
VdtcTreeItemBase *parent = GetParent(b);
if(parent)
AppendPathRecursively(parent, dir, useRoot);
else
{
// no parent assume top node
if(b->IsRoot() && useRoot)
dir.AssignDir(b->GetName());
return;
}
// now we are unwinding the other way around
if(b->IsDir())
dir.AppendDir(b->GetName());
else if(b->IsFile())
dir.SetFullName(b->GetName());
};
// -- event handlers --
void wxVirtualDirTreeCtrl::OnExpanding(wxTreeEvent &event)
{
// check for collapsing item, and scan from there
wxTreeItemId id = event.GetItem();
if(id.IsOk())
{
VdtcTreeItemBase *t = (VdtcTreeItemBase *)GetItemData(id);
if(t && t->IsDir())
{
// extract data element belonging to it, and scan.
ScanFromDir(t, GetFullPath(id), VDTC_MIN_SCANDEPTH);
// TODO: When this scan gives no nodes, delete all children
// and conclude that the scan could not be performed upon expansion
}
}
// be kind, and let someone else also handle this event
event.Skip();
}
wxBitmap *wxVirtualDirTreeCtrl::CreateRootBitmap()
{
// create root and return
return new wxBitmap(xpm_root);
}
wxBitmap *wxVirtualDirTreeCtrl::CreateFolderBitmap()
{
// create folder and return
return new wxBitmap(xpm_folder);
}
wxBitmap *wxVirtualDirTreeCtrl::CreateFileBitmap()
{
// create file and return
return new wxBitmap(xpm_file);
}
VdtcTreeItemBase *wxVirtualDirTreeCtrl::AddFileItem(const wxString &name)
{
// call the file item node create method
return OnCreateTreeItem(VDTC_TI_FILE, name);
}
VdtcTreeItemBase *wxVirtualDirTreeCtrl::AddDirItem(const wxString &name)
{
// call the dir item node create method
return OnCreateTreeItem(VDTC_TI_DIR, name);
}
// --- virtual handlers ----
void wxVirtualDirTreeCtrl::OnAssignIcons(wxImageList &icons)
{
wxBitmap *bmp;
// default behaviour, assign three bitmaps
bmp = CreateRootBitmap();
icons.Add(*bmp);
delete bmp;
// 1 = folder
bmp = CreateFolderBitmap();
icons.Add(*bmp);
delete bmp;
// 2 = file
bmp = CreateFileBitmap();
icons.Add(*bmp);
delete bmp;
}
VdtcTreeItemBase *wxVirtualDirTreeCtrl::OnCreateTreeItem(int type, const wxString &name)
{
// return a default instance, no extra info needed in this item
return new VdtcTreeItemBase(type, name);
}
bool wxVirtualDirTreeCtrl::OnAddRoot(VdtcTreeItemBase &WXUNUSED(item), const wxFileName &WXUNUSED(name))
{
// allow adding
return true;
}
bool wxVirtualDirTreeCtrl::OnDirectoryScanBegin(const wxFileName &WXUNUSED(path))
{
// allow all paths
return true;
}
bool wxVirtualDirTreeCtrl::OnAddFile(VdtcTreeItemBase &WXUNUSED(item), const wxFileName &WXUNUSED(name))
{
// allow all files
return true;
}
bool wxVirtualDirTreeCtrl::OnAddDirectory(VdtcTreeItemBase &WXUNUSED(item), const wxFileName &WXUNUSED(name))
{
// allow all dirs
return true;
}
void wxVirtualDirTreeCtrl::OnSetRootPath(const wxString &WXUNUSED(root))
{
// do nothing here, but it can be used to start initialisation
// based upon the setting of the root (which means a renewal from the tree)
}
void wxVirtualDirTreeCtrl::OnAddedItems(const wxTreeItemId &WXUNUSED(parent))
{
return;
}
void wxVirtualDirTreeCtrl::OnDirectoryScanEnd(VdtcTreeItemBaseArray &WXUNUSED(items), const wxFileName &WXUNUSED(path))
{
return;
}