guiFileTreeCtrl.cpp
Engine/source/gui/controls/guiFileTreeCtrl.cpp
Public Functions
_dumpFiles(const char * path, Vector< StringTableEntry > & directoryVector, S32 depth)
bool
_hasChildren(const char * path)
bool
_isDirInMainDotCsPath(const char * dir)
ConsoleDocClass(GuiFileTreeCtrl , "@brief A <a href="/coding/file/guieditctrl_8cpp/#guieditctrl_8cpp_1abb04e3738c4c5a96b3ade6fa47013a6c">control</a> that displays <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> hierarchical tree view of <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> path in the game <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a702945180aa732857b380a007a7e2a21">file</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">system.\n\n</a>" "@note Currently not used, most likely existed <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> editors. Possibly <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">deprecated.\n\n</a>" " @internal" )
DefineEngineMethod(GuiFileTreeCtrl , getSelectedPath , const char * , () , "getSelectedPath() - returns the currently selected path in the tree" )
DefineEngineMethod(GuiFileTreeCtrl , reload , void , () , "() - Reread the directory tree hierarchy." )
DefineEngineMethod(GuiFileTreeCtrl , setSelectedPath , bool , (const char *path) , "setSelectedPath(path) - expands the tree <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the specified path" )
Detailed Description
Public Functions
_dumpFiles(const char * path, Vector< StringTableEntry > & directoryVector, S32 depth)
_hasChildren(const char * path)
_isDirInMainDotCsPath(const char * dir)
ConsoleDocClass(GuiFileTreeCtrl , "@brief A <a href="/coding/file/guieditctrl_8cpp/#guieditctrl_8cpp_1abb04e3738c4c5a96b3ade6fa47013a6c">control</a> that displays <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> hierarchical tree view of <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> path in the game <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a702945180aa732857b380a007a7e2a21">file</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">system.\n\n</a>" "@note Currently not used, most likely existed <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> editors. Possibly <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">deprecated.\n\n</a>" " @internal" )
DefineEngineMethod(GuiFileTreeCtrl , getSelectedPath , const char * , () , "getSelectedPath() - returns the currently selected path in the tree" )
DefineEngineMethod(GuiFileTreeCtrl , reload , void , () , "() - Reread the directory tree hierarchy." )
DefineEngineMethod(GuiFileTreeCtrl , setSelectedPath , bool , (const char *path) , "setSelectedPath(path) - expands the tree <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the specified path" )
IMPLEMENT_CONOBJECT(GuiFileTreeCtrl )
1 2//----------------------------------------------------------------------------- 3// Copyright (c) 2012 GarageGames, LLC 4// 5// Permission is hereby granted, free of charge, to any person obtaining a copy 6// of this software and associated documentation files (the "Software"), to 7// deal in the Software without restriction, including without limitation the 8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 9// sell copies of the Software, and to permit persons to whom the Software is 10// furnished to do so, subject to the following conditions: 11// 12// The above copyright notice and this permission notice shall be included in 13// all copies or substantial portions of the Software. 14// 15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 21// IN THE SOFTWARE. 22//----------------------------------------------------------------------------- 23 24#include "gui/controls/guiFileTreeCtrl.h" 25#include "core/strings/findMatch.h" 26#include "core/frameAllocator.h" 27#include "core/strings/stringUnit.h" 28#include "console/consoleTypes.h" 29#include "console/engineAPI.h" 30 31 32IMPLEMENT_CONOBJECT(GuiFileTreeCtrl); 33 34ConsoleDocClass( GuiFileTreeCtrl, 35 "@brief A control that displays a hierarchical tree view of a path in the game file system.\n\n" 36 "@note Currently not used, most likely existed for editors. Possibly deprecated.\n\n" 37 "@internal" 38); 39 40 41static bool _isDirInMainDotCsPath(const char* dir) 42{ 43 StringTableEntry cs = Platform::getMainDotCsDir(); 44 U32 len = dStrlen(cs) + dStrlen(dir) + 2; 45 FrameTemp<UTF8> fullpath(len); 46 dSprintf(fullpath, len, "%s/%s", cs, dir); 47 48 return Platform::isDirectory(fullpath); 49} 50 51static bool _hasChildren(const char* path) 52{ 53 if( Platform::hasSubDirectory(path)) 54 return true; 55 56 Vector<StringTableEntry> dummy; 57 Platform::dumpDirectories( path, dummy, 0, true); 58 59 return dummy.size() > 0; 60} 61 62GuiFileTreeCtrl::GuiFileTreeCtrl() 63 : Parent() 64{ 65 // Parent configuration 66 setBounds(0,0,200,100); 67 mDestroyOnSleep = false; 68 mSupportMouseDragging = false; 69 mMultipleSelections = false; 70 71 mFileFilter = "*." TORQUE_SCRIPT_EXTENSION " *.gui *.ed." TORQUE_SCRIPT_EXTENSION; 72 _initFilters(); 73} 74 75void GuiFileTreeCtrl::initPersistFields() 76{ 77 addGroup( "File Tree" ); 78 addField( "rootPath", TypeRealString, Offset( mRootPath, GuiFileTreeCtrl ), "Path in game directory that should be displayed in the control." ); 79 addProtectedField( "fileFilter", TypeRealString, Offset( mFileFilter, GuiFileTreeCtrl ), 80 &_setFileFilterValue, &defaultProtectedGetFn, "Vector of file patterns. If not empty, only files matching the pattern will be shown in the control." ); 81 endGroup( "File Tree" ); 82 83 Parent::initPersistFields(); 84} 85 86static void _dumpFiles(const char *path, Vector<StringTableEntry> &directoryVector, S32 depth = 0) 87{ 88 Vector<Platform::FileInfo> fileVec; 89 Platform::dumpPath( path, fileVec, depth); 90 91 for(U32 i = 0; i < fileVec.size(); i++) 92 { 93 directoryVector.push_back( StringTable->insert(fileVec[i].pFileName) ); 94 } 95} 96 97void GuiFileTreeCtrl::updateTree() 98{ 99 // Kill off any existing items 100 _destroyTree(); 101 102 // Here we're going to grab our system volumes from the platform layer and create them as roots 103 // 104 // Note : that we're passing a 1 as the last parameter to Platform::dumpDirectories, which tells it 105 // how deep to dump in recursion. This is an optimization to keep from dumping the whole file system 106 // to the tree. The tree will dump more paths as necessary when the virtual parents are expanded, 107 // much as windows does. 108 109 // Determine the root path. 110 111 String rootPath = Platform::getMainDotCsDir(); 112 if( !mRootPath.isEmpty() ) 113 rootPath = String::ToString( "%s/%s", rootPath.c_str(), mRootPath.c_str() ); 114 115 // get the files in the main.tscript dir 116 Vector<StringTableEntry> pathVec; 117 Platform::dumpDirectories( rootPath, pathVec, 0, true); 118 _dumpFiles( rootPath, pathVec, 0); 119 if( ! pathVec.empty() ) 120 { 121 // get the last folder in the path. 122 char *dirname = dStrdup(rootPath); 123 U32 last = dStrlen(dirname)-1; 124 if(dirname[last] == '/') 125 dirname[last] = '\0'; 126 char* lastPathComponent = dStrrchr(dirname,'/'); 127 if(lastPathComponent) 128 *lastPathComponent++ = '\0'; 129 else 130 lastPathComponent = dirname; 131 132 // Iterate through the returned paths and add them to the tree 133 Vector<StringTableEntry>::iterator j = pathVec.begin(); 134 for( ; j != pathVec.end(); j++ ) 135 { 136 char fullModPathSub [512]; 137 dMemset( fullModPathSub, 0, 512 ); 138 dSprintf( fullModPathSub, 512, "%s/%s", lastPathComponent, (*j) ); 139 addPathToTree( *j ); 140 } 141 dFree(dirname); 142 } 143} 144 145bool GuiFileTreeCtrl::onWake() 146{ 147 if( !Parent::onWake() ) 148 return false; 149 150 updateTree(); 151 152 return true; 153} 154 155bool GuiFileTreeCtrl::onVirtualParentExpand(Item *item) 156{ 157 if( !item || !item->isExpanded() ) 158 return true; 159 160 const char* pathToExpand = item->getValue(); 161 if( !pathToExpand ) 162 { 163 Con::errorf("GuiFileTreeCtrl::onVirtualParentExpand - Unable to retrieve item value!"); 164 return false; 165 } 166 167 Vector<StringTableEntry> pathVec; 168 _dumpFiles( pathToExpand, pathVec, 0 ); 169 Platform::dumpDirectories( pathToExpand, pathVec, 0, true); 170 if( ! pathVec.empty() ) 171 { 172 // Iterate through the returned paths and add them to the tree 173 Vector<StringTableEntry>::iterator i = pathVec.begin(); 174 for( ; i != pathVec.end(); i++ ) 175 recurseInsert(item, (*i) ); 176 177 item->setExpanded( true ); 178 } 179 180 item->setVirtualParent( false ); 181 182 // Update our tree view 183 buildVisibleTree(); 184 185 return true; 186 187} 188 189void GuiFileTreeCtrl::addPathToTree( StringTableEntry path ) 190{ 191 if( !path ) 192 { 193 Con::errorf("GuiFileTreeCtrl::addPathToTree - Invalid Path!"); 194 return; 195 } 196 197 // Identify which root (volume) this path belongs to (if any) 198 S32 root = getFirstRootItem(); 199 StringTableEntry ourPath = &path[ dStrcspn( path, "/" ) + 1]; 200 StringTableEntry ourRoot = StringUnit::getUnit( path, 0, "/" ); 201 // There are no current roots, we can safely create one 202 if( root == 0 ) 203 { 204 recurseInsert( NULL, path ); 205 } 206 else 207 { 208 while( root != 0 ) 209 { 210 if( dStricmp( getItemValue( root ), ourRoot ) == 0 ) 211 { 212 recurseInsert( getItem( root ), ourPath ); 213 break; 214 } 215 root = this->getNextSiblingItem( root ); 216 } 217 // We found none so we'll create one 218 if ( root == 0 ) 219 { 220 recurseInsert( NULL, path ); 221 } 222 } 223} 224 225void GuiFileTreeCtrl::onItemSelected( Item *item ) 226{ 227 Con::executef( this, "onSelectPath", avar("%s",item->getValue()) ); 228 229 mSelPath = item->getValue(); 230 if( _hasChildren( mSelPath ) ) 231 item->setVirtualParent( true ); 232} 233 234bool GuiFileTreeCtrl::_setFileFilterValue( void *object, const char *index, const char *data ) 235{ 236 GuiFileTreeCtrl* ctrl = ( GuiFileTreeCtrl* ) object; 237 238 ctrl->mFileFilter = data; 239 ctrl->_initFilters(); 240 241 return false; 242} 243 244void GuiFileTreeCtrl::_initFilters() 245{ 246 mFilters.clear(); 247 248 U32 index = 0; 249 while( true ) 250 { 251 const char* pattern = StringUnit::getUnit( mFileFilter, index, " " ); 252 if( !pattern[ 0 ] ) 253 break; 254 255 mFilters.push_back( pattern ); 256 ++ index; 257 } 258} 259 260bool GuiFileTreeCtrl::matchesFilters(const char* filename) 261{ 262 if( !mFilters.size() ) 263 return true; 264 265 for(S32 i = 0; i < mFilters.size(); i++) 266 { 267 if(FindMatch::isMatch( mFilters[i], filename)) 268 return true; 269 } 270 return false; 271} 272 273void GuiFileTreeCtrl::recurseInsert( Item* parent, StringTableEntry path ) 274{ 275 if( !path ) 276 return; 277 278 char szPathCopy [ 1024 ]; 279 dMemset( szPathCopy, 0, 1024 ); 280 dStrcpy( szPathCopy, path, 1024 ); 281 282 // Jump over the first character if it's a root / 283 char *curPos = szPathCopy; 284 if( *curPos == '/' ) 285 curPos++; 286 287 char szValue[1024]; 288 dMemset( szValue, 0, 1024 ); 289 if( parent ) 290 { 291 dMemset( szValue, 0, sizeof( szValue ) ); 292 dSprintf( szValue, sizeof( szValue ), "%s/%s", parent->getValue(), curPos ); 293 } 294 else 295 { 296 dStrncpy( szValue, curPos, sizeof( szValue ) ); 297 szValue[ sizeof( szValue ) - 1 ] = 0; 298 } 299 300 const U32 valueLen = dStrlen( szValue ); 301 char* value = new char[ valueLen + 1 ]; 302 dMemcpy( value, szValue, valueLen + 1 ); 303 304 char *delim = dStrchr( curPos, '/' ); 305 if ( delim ) 306 { 307 // terminate our / and then move our pointer to the next character (rest of the path) 308 *delim = 0x00; 309 delim++; 310 } 311 S32 itemIndex = 0; 312 // only insert blindly if we have no root 313 if( !parent ) 314 itemIndex = insertItem( 0, curPos, curPos ); 315 else 316 { 317 bool allowed = (_isDirInMainDotCsPath(value) || matchesFilters(value)); 318 Item *exists = parent->findChildByValue( szValue ); 319 if( allowed && !exists && String::compare( curPos, "" ) != 0 ) 320 { 321 // Since we're adding a child this parent can't be a virtual parent, so clear that flag 322 parent->setVirtualParent( false ); 323 324 itemIndex = insertItem( parent->getID(), curPos); 325 Item *newitem = getItem(itemIndex); 326 newitem->setValue( value ); 327 } 328 else 329 { 330 itemIndex = ( parent != NULL ) ? ( ( exists != NULL ) ? exists->getID() : -1 ) : -1; 331 } 332 } 333 334 Item *newitem = getItem(itemIndex); 335 if(newitem) 336 { 337 newitem->setValue( value ); 338 if( _isDirInMainDotCsPath( value ) ) 339 { 340 newitem->setNormalImage( Icon_FolderClosed ); 341 newitem->setExpandedImage( Icon_Folder ); 342 newitem->setVirtualParent(true); 343 newitem->setExpanded(false); 344 } 345 else 346 { 347 newitem->setNormalImage( Icon_Doc ); 348 } 349 } 350 // since we're only dealing with volumes and directories, all end nodes will be virtual parents 351 // so if we are at the bottom of the rabbit hole, set the item to be a virtual parent 352 Item* item = getItem( itemIndex ); 353 if(item) 354 { 355 item->setExpanded(false); 356 if(parent && _isDirInMainDotCsPath(item->getValue()) && Platform::hasSubDirectory(item->getValue())) 357 item->setVirtualParent(true); 358 } 359 if( delim ) 360 { 361 if( ( String::compare( delim, "" ) == 0 ) && item ) 362 { 363 item->setExpanded( false ); 364 if( parent && _hasChildren( item->getValue() ) ) 365 item->setVirtualParent( true ); 366 } 367 } 368 else 369 { 370 if( item ) 371 { 372 item->setExpanded( false ); 373 if( parent && _hasChildren( item->getValue() ) ) 374 item->setVirtualParent( true ); 375 } 376 } 377 378 // Down the rabbit hole we go 379 recurseInsert( getItem( itemIndex ), delim ); 380 381} 382 383DefineEngineMethod( GuiFileTreeCtrl, getSelectedPath, const char*, (), , "getSelectedPath() - returns the currently selected path in the tree") 384{ 385 const String& path = object->getSelectedPath(); 386 return Con::getStringArg( path ); 387} 388 389DefineEngineMethod( GuiFileTreeCtrl, setSelectedPath, bool, (const char * path), , "setSelectedPath(path) - expands the tree to the specified path") 390{ 391 return object->setSelectedPath( path ); 392} 393 394DefineEngineMethod( GuiFileTreeCtrl, reload, void, (), , "() - Reread the directory tree hierarchy." ) 395{ 396 object->updateTree(); 397} 398 399bool GuiFileTreeCtrl::setSelectedPath( const char* path ) 400{ 401 if( !path ) 402 return false; 403 404 // Since we only list one deep on paths, we need to add the path to the tree just incase it isn't already indexed in the tree 405 // or else we wouldn't be able to select a path we hadn't previously browsed to. :) 406 if( _isDirInMainDotCsPath( path ) ) 407 addPathToTree( path ); 408 409 // see if we have a child that matches what we want 410 for(U32 i = 0; i < mItems.size(); i++) 411 { 412 if( dStricmp( mItems[i]->getValue(), path ) == 0 ) 413 { 414 Item* item = mItems[i]; 415 AssertFatal(item,"GuiFileTreeCtrl::setSelectedPath - Item Index Bad, Fatal Mistake!!!"); 416 item->setExpanded( true ); 417 clearSelection(); 418 setItemSelected( item->getID(), true ); 419 // make sure all of it's parents are expanded 420 S32 parent = getParentItem( item->getID() ); 421 while( parent != 0 ) 422 { 423 setItemExpanded( parent, true ); 424 parent = getParentItem( parent ); 425 } 426 // Rebuild our tree just incase we've oops'd 427 buildVisibleTree(); 428 scrollVisible( item ); 429 } 430 } 431 return false; 432} 433