1/* 2 GNUstep ProjectCenter - http://www.gnustep.org/experience/ProjectCenter.html 3 4 Copyright (C) 2000-2004 Free Software Foundation 5 6 Authors: Philippe C.D. Robert 7 Serg Stoyan 8 9 This file is part of GNUstep. 10 11 This application is free software; you can redistribute it and/or 12 modify it under the terms of the GNU General Public 13 License as published by the Free Software Foundation; either 14 version 2 of the License, or (at your option) any later version. 15 16 This application is distributed in the hope that it will be useful, 17 but WITHOUT ANY WARRANTY; without even the implied warranty of 18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 19 Library General Public License for more details. 20 21 You should have received a copy of the GNU General Public 22 License along with this library; if not, write to the Free 23 Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA. 24*/ 25 26#import <ProjectCenter/PCDefines.h> 27#import <ProjectCenter/PCFileManager.h> 28#import <ProjectCenter/PCProjectManager.h> 29#import <ProjectCenter/PCProject.h> 30#import <ProjectCenter/PCProjectBrowser.h> 31#import <ProjectCenter/PCProjectEditor.h> 32#import <ProjectCenter/PCFileNameField.h> 33 34#import <ProjectCenter/PCLogController.h> 35 36#import "Modules/Preferences/Misc/PCMiscPrefs.h" 37 38NSString *PCBrowserDidSetPathNotification = @"PCBrowserDidSetPathNotification"; 39 40@implementation PCProjectBrowser 41 42// ============================================================================ 43// ==== Intialization & deallocation 44// ============================================================================ 45 46- (id)initWithProject:(PCProject *)aProject 47{ 48 if ((self = [super init])) 49 { 50 project = aProject; 51 52 browser = [[NSBrowser alloc] initWithFrame:NSMakeRect(-10,-10,256,128)]; 53 [browser setRefusesFirstResponder:YES]; 54// [browser setAutoresizingMask: NSViewWidthSizable | NSViewMinYMargin]; 55 [browser setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable]; 56 [browser setTitled:NO]; 57 [browser setMaxVisibleColumns:4]; 58 [browser setSeparatesColumns:NO]; 59 [browser setAllowsMultipleSelection:YES]; 60 [browser setDelegate:self]; 61 [browser setTarget:self]; 62 [browser setAction:@selector(click:)]; 63 [browser setDoubleAction:@selector(doubleClick:)]; 64 [browser setRefusesFirstResponder:YES]; 65 [browser loadColumnZero]; 66 67 [[NSNotificationCenter defaultCenter] 68 addObserver:self 69 selector:@selector(projectDictDidChange:) 70 name:PCProjectDictDidChangeNotification 71 object:nil]; 72 } 73 74 return self; 75} 76 77- (void)dealloc 78{ 79#ifdef DEVELOPMENT 80 NSLog (@"PCProjectBrowser: dealloc"); 81#endif 82 83 [[NSNotificationCenter defaultCenter] removeObserver:self]; 84 85 RELEASE(browser); 86 87 [super dealloc]; 88} 89 90// ============================================================================ 91// ==== Accessory methods 92// ============================================================================ 93 94- (NSView *)view 95{ 96 return browser; 97} 98 99// Returns nil if multiple files or category selected 100- (NSString *)nameOfSelectedFile 101{ 102 NSString *name = [[browser path] lastPathComponent]; 103 NSString *category = [self nameOfSelectedCategory]; 104 NSMutableArray *pathArray; 105 NSEnumerator *enumerator; 106 NSString *pathItem; 107 108// NSLog(@"---> Selected: %@: category: %@", name, category); 109 110 if ([[browser selectedCells] count] != 1 || 111 !category || 112 [name isEqualToString:category]) 113 { 114 return nil; 115 } 116 117 pathArray = [[[browser path] pathComponents] mutableCopy]; 118 enumerator = [pathArray objectEnumerator]; 119 while ((pathItem = [enumerator nextObject])) 120 { 121 if ([pathItem isEqualToString:category]) 122 { 123 name = [enumerator nextObject]; 124 break; 125 } 126 } 127 RELEASE(pathArray); 128 129 return name; 130} 131 132// Returns nil if multiple files selected 133- (NSString *)pathToSelectedFile 134{ 135 NSString *name = [self nameOfSelectedFile]; 136 NSString *path = [browser path]; 137 138 if (!name) 139 { 140 path = nil; 141 } 142 143 return path; 144} 145 146// Returns 'nil' if selected: 147// - root project (browser path is @"/") 148// - multiple categories 149// - name of subproject 150// Should not call any of the nameOf... or pathTo... methods to prevent 151// cyclic recursion. 152- (NSString *)nameOfSelectedCategory 153{ 154 NSArray *pathArray = [[browser path] componentsSeparatedByString:@"/"]; 155 NSString *lastPathElement = [[browser path] lastPathComponent]; 156 PCProject *activeProject = [[project projectManager] activeProject]; 157 NSArray *rootCategories = [activeProject rootCategories]; 158 NSString *name = nil; 159 int i; 160 161 // Name of subproject selected: Change active project to superproject 162 // to check category against superproject's catgory list. 163 // But: path '/Subproject/Foo' and '/Subprojects/Foo/Subprojects' will 164 // return the same category 'Subprojects' and active project will be 'Foo' 165 // in both cases 166// ![[self nameOfSelectedFile] isEqualToString:lastPathElement]) 167/* if ([lastPathElement isEqualToString:[activeProject projectName]]) 168 { 169 activeProject = [activeProject superProject]; 170 rootCategories = [activeProject rootCategories]; 171 }*/ 172 173 // Multiple categories selected 174 if (([rootCategories containsObject:lastPathElement] 175 && [[browser selectedCells] count] > 1)) 176 { 177 return nil; 178 } 179 180 for (i = [pathArray count] - 1; i >= 0; i--) 181 { 182 if ([rootCategories containsObject:[pathArray objectAtIndex:i]]) 183 { 184 name = [pathArray objectAtIndex:i]; 185 break; 186 } 187 } 188 189 // Subproject's name selected 190 if ([name isEqualToString:@"Subprojects"] && 191 [lastPathElement isEqualToString:[activeProject projectName]]) 192 { 193 return nil; 194 } 195 196 return name; 197} 198 199// Returns nil of multiple categories selected 200- (NSString *)pathToSelectedCategory 201{ 202 NSString *path = nil; 203 NSString *selectedCategory = [self nameOfSelectedCategory]; 204 NSMutableArray *bPathArray = nil; 205 int i; 206 207 if (selectedCategory) 208 { 209 bPathArray = [NSMutableArray arrayWithArray:[[browser path] 210 componentsSeparatedByString:@"/"]]; 211 i = [bPathArray count] - 1; 212 while (![[bPathArray objectAtIndex:i] isEqualToString:selectedCategory]) 213 { 214 [bPathArray removeObjectAtIndex:i]; 215 i = [bPathArray count] - 1; 216 } 217 path = [bPathArray componentsJoinedByString:@"/"]; 218 } 219 220 return path; 221} 222 223// Returns nil of multiple categories selected 224- (NSString *)pathFromSelectedCategory 225{ 226 NSString *selectedCategory = [self nameOfSelectedCategory]; 227 NSMutableArray *bPathArray; 228 NSString *path = nil; 229 230 if (selectedCategory) 231 { 232 bPathArray = 233 [[[browser path] componentsSeparatedByString:@"/"] mutableCopy]; 234 while (![[bPathArray objectAtIndex:1] isEqualToString:selectedCategory]) 235 { 236 [bPathArray removeObjectAtIndex:1]; 237 } 238 path = [bPathArray componentsJoinedByString:@"/"]; 239 RELEASE(bPathArray); 240 } 241 242 return path; 243} 244 245- (NSString *)nameOfSelectedRootCategory 246{ 247 NSString *categoryPath = [self pathToSelectedCategory]; 248 NSArray *pathComponents; 249 250 if ([categoryPath isEqualToString:@"/"] || [categoryPath isEqualToString:@""]) 251 { 252 return nil; 253 } 254 255 pathComponents = [categoryPath componentsSeparatedByString:@"/"]; 256 257 return [pathComponents objectAtIndex:1]; 258} 259 260- (NSArray *)selectedFiles 261{ 262 NSArray *cells = [browser selectedCells]; 263 NSMutableArray *files = [[NSMutableArray alloc] initWithCapacity: 1]; 264 int i; 265 int count = [cells count]; 266 PCProject *activeProject = [[project projectManager] activeProject]; 267 268 // Return nil if categories selected 269 if ([cells count] == 0 270 || [[activeProject rootCategories] 271 containsObject:[[cells objectAtIndex:0] stringValue]]) 272 { 273 return nil; 274 } 275 276 for (i = 0; i < count; i++) 277 { 278 [files addObject: [[cells objectAtIndex: i] stringValue]]; 279 } 280 281 return AUTORELEASE((NSArray *)files); 282} 283 284- (NSString *)path 285{ 286 return [browser path]; 287} 288 289- (BOOL)setPath:(NSString *)path 290{ 291 BOOL res; 292 293 if ([[browser path] isEqualToString: path]) 294 { 295 return YES; 296 } 297 298// PCLogInfo(self, @"[setPath]: %@", path); 299 300 res = [browser setPath:path]; 301 302 [[NSNotificationCenter defaultCenter] 303 postNotificationName:PCBrowserDidSetPathNotification 304 object:self]; 305 306 return res; 307} 308 309- (void)reloadLastColumnAndNotify:(BOOL)yn 310{ 311 NSInteger column = [browser lastColumn]; 312 NSString *category = [self nameOfSelectedCategory]; 313 NSInteger selectedColumn = [browser selectedColumn]; 314 NSMatrix *colMatrix = [browser matrixInColumn:selectedColumn]; 315 NSInteger rowCount = 0, colCount = 0, spCount = 0; 316 PCProject *activeProject = [[project projectManager] activeProject]; 317 NSString *selCellTitle = [[browser selectedCell] stringValue]; 318 319 if ([category isEqualToString:@"Subprojects"] 320 && ![selCellTitle isEqualToString:@"Subprojects"]) 321 { // /Subprojects/Name selected 322 if ([selCellTitle isEqualToString:[activeProject projectName]]) 323 { 324 activeProject = [activeProject superProject]; 325 } 326 [colMatrix getNumberOfRows:&rowCount columns:&colCount]; 327 spCount = [[[activeProject projectDict] 328 objectForKey:PCSubprojects] count]; 329 } 330 331 if ([category isEqualToString:@"Subprojects"] && rowCount != spCount 332 && ![[[browser selectedCell] stringValue] isEqualToString:@"Subprojects"]) 333 { 334 column = selectedColumn; 335 } 336 337 [browser reloadColumn:column]; 338 339 if (yn) 340 { 341 [[NSNotificationCenter defaultCenter] 342 postNotificationName:PCBrowserDidSetPathNotification 343 object:self]; 344 } 345} 346 347- (void)reloadLastColumnAndSelectFile:(NSString *)file 348{ 349 PCProject *p = [[project projectManager] activeProject]; 350 NSString *catKey = [p keyForCategory:[self nameOfSelectedCategory]]; 351 NSArray *array = [[p projectDict] objectForKey:catKey]; 352 NSString *path = [self path]; 353 NSString *tmp; 354 355 // Determine last column with files (removing classes and methods from path) 356 tmp = [[path lastPathComponent] substringWithRange:NSMakeRange(0,1)]; 357 while ([tmp isEqualToString:@"@"] // classes 358 || [tmp isEqualToString:@"+"] // factory methods 359 || [tmp isEqualToString:@"-"]) // instance methods 360 { 361 path = [path stringByDeletingLastPathComponent]; 362 tmp = [[path lastPathComponent] substringWithRange:NSMakeRange(0,1)]; 363 } 364 365 NSLog(@"PCBrowser set path: %@", path); 366 [self setPath:[path stringByDeletingLastPathComponent]]; 367 [self reloadLastColumnAndNotify:NO]; 368 369 [browser selectRow:[array indexOfObject:file] inColumn:[browser lastColumn]]; 370 371 // Notify 372 [[NSNotificationCenter defaultCenter] 373 postNotificationName:PCBrowserDidSetPathNotification 374 object:self]; 375} 376 377// ============================================================================ 378// ==== Actions 379// ============================================================================ 380 381- (void)click:(id)sender 382{ 383 NSString *category; 384 PCProject *activeProject; 385 NSString *browserPath; 386 NSString *filePath; 387 NSString *fileName; 388 389 if (sender != browser) 390 { 391 return; 392 } 393 394 category = [self nameOfSelectedCategory]; 395 activeProject = [[project projectManager] activeProject]; 396 browserPath = [self path]; 397 filePath = [self pathToSelectedFile]; 398 fileName = [self nameOfSelectedFile]; 399 400 NSLog(@"[click] category: %@ forProject: %@ fileName: %@", 401 category, [activeProject projectName], fileName); 402 403// ![fileName isEqualToString:[activeProject projectName]] && 404 if (filePath && 405 [filePath isEqualToString:browserPath] && 406 category && 407 ![category isEqualToString:@"Libraries"] 408 ) 409 { 410 NSLog(@"[click] category: %@ filePath: %@", category, filePath); 411 [[activeProject projectEditor] openEditorForCategoryPath:browserPath 412 windowed:NO]; 413 } 414 415 [[NSNotificationCenter defaultCenter] 416 postNotificationName:PCBrowserDidSetPathNotification 417 object:self]; 418} 419 420- (void)doubleClick:(id)sender 421{ 422 NSString *category = [self nameOfSelectedCategory]; 423 id selectedCell; 424 NSString *fileName; 425 PCProject *activeProject; 426 NSString *key; 427 NSString *filePath; 428 id <PCPreferences> prefs = [[project projectManager] prefController]; 429 NSWorkspace *workspace; 430 NSString *appName, *type; 431 432 if ((sender != browser) || [category isEqualToString:@"Libraries"]) 433 { 434 return; 435 } 436 437 selectedCell = [sender selectedCell]; 438 fileName = [[sender selectedCell] stringValue]; 439 activeProject = [[project projectManager] activeProject]; 440 key = [activeProject keyForCategory:category]; 441 filePath = [activeProject pathForFile:fileName forKey:key]; 442 443 if ([self nameOfSelectedFile] != nil) 444 { 445 BOOL foundApp = NO; 446 // PCLogInfo(self, @"{doubleClick} filePath: %@", filePath);*/ 447 448 workspace = [NSWorkspace sharedWorkspace]; 449 foundApp = [workspace getInfoForFile:filePath 450 application:&appName 451 type:&type]; 452 // NSLog (@"Open file: %@ with app: %@", filePath, appName); 453 454 // If 'Editor' role was set in .GNUstepExtPrefs application 455 // name will be returned according that setting. Otherwise 456 // 'ProjectCenter.app' will be returned accoring to NSTypes 457 // from Info-gnustep.plist file of PC. 458 if(foundApp == NO || [appName isEqualToString:@"ProjectCenter.app"]) 459 { 460 appName = [prefs stringForKey:Editor]; 461 462 if (![appName isEqualToString:@"ProjectCenter"]) 463 { 464 [workspace openFile:filePath 465 withApplication:appName]; 466 } 467 else 468 { 469 [[activeProject projectEditor] 470 openEditorForCategoryPath:[self path] 471 windowed:YES]; 472 } 473 } 474 else 475 { 476 [workspace openFile:filePath]; 477 } 478 } 479 else 480 { 481 if ([[selectedCell title] isEqualToString:@"Subprojects"]) 482 { 483 [[project projectManager] addSubproject]; 484 } 485 else 486 { 487 [[project projectManager] addProjectFiles]; 488 } 489 } 490} 491 492// ============================================================================ 493// ==== Notifications 494// ============================================================================ 495 496- (void)projectDictDidChange:(NSNotification *)aNotif 497{ 498 NSDictionary *notifObject = [aNotif object]; 499 PCProject *changedProject = [notifObject objectForKey:@"Project"]; 500 NSString *changedAttribute = [notifObject objectForKey:@"Attribute"]; 501 502 if (!browser) 503 { 504 return; 505 } 506 507 if (changedProject != project 508 && changedProject != [project activeSubproject] 509 && [changedProject superProject] != [project activeSubproject]) 510 { 511 return; 512 } 513 514// NSLog(@"PCPB: projectDictDidChange in %@ (%@)", 515// [changedProject projectName], [project projectName]); 516 517 // If project dictionary changed after files adding/removal, 518 // refresh file list 519 if ([[changedProject rootKeys] containsObject:changedAttribute]) 520 { 521 [self reloadLastColumnAndNotify:YES]; 522 } 523} 524 525@end 526 527@implementation PCProjectBrowser (ProjectBrowserDelegate) 528 529- (void) browser:(NSBrowser *)sender 530 createRowsForColumn:(NSInteger)column 531 inMatrix:(NSMatrix *)matrix 532{ 533 NSString *pathToCol; 534 NSArray *files; 535 NSUInteger i = 0; 536 NSUInteger count = 0; 537 538 if (sender != browser || !matrix || ![matrix isKindOfClass:[NSMatrix class]]) 539 { 540 return; 541 } 542 543 pathToCol = [sender pathToColumn:column]; 544 files = [project contentAtCategoryPath:pathToCol]; 545 if (files) 546 { 547 count = [files count]; 548 } 549 550 for (i = 0; i < count; ++i) 551 { 552 NSMutableString *categoryPath = nil; 553 id cell; 554 555 categoryPath = [NSMutableString stringWithString:pathToCol]; 556 557 [matrix insertRow:i]; 558 559 cell = [matrix cellAtRow:i column:0]; 560 [cell setStringValue:[files objectAtIndex:i]]; 561 562 if (![categoryPath isEqualToString:@"/"]) 563 { 564 [categoryPath appendString:@"/"]; 565 } 566 [categoryPath appendString:[files objectAtIndex:i]]; 567 568 [cell setLeaf:![project hasChildrenAtCategoryPath:categoryPath]]; 569 [cell setRefusesFirstResponder:YES]; 570 } 571} 572 573@end 574 575@implementation PCProjectBrowser (FileNameIconDelegate) 576 577// If file was opened in editor: 578// 1. Determine editor 579// 2. Ask editor for icon 580- (NSImage *)_editorIconImageForFile:(NSString *)fileName 581{ 582 PCProjectEditor *projectEditor = [project projectEditor]; 583 id<CodeEditor> editor = nil; 584 NSString *categoryName = [self nameOfSelectedCategory]; 585 NSString *categoryKey = [project keyForCategory:categoryName]; 586 NSString *filePath; 587 588 filePath = [project pathForFile:fileName forKey:categoryKey]; 589 editor = [projectEditor editorForFile:filePath]; 590 if (editor != nil) 591 { 592 return [editor fileIcon]; 593 } 594 595 return nil; 596} 597 598- (NSImage *)fileNameIconImage 599{ 600 NSString *categoryName = nil; 601 NSString *fileName = nil; 602 NSString *fileExtension = nil; 603 NSString *iconName = nil; 604 NSImage *icon = nil; 605 PCProject *activeProject = [[project projectManager] activeProject]; 606 607 fileName = [self nameOfSelectedFile]; 608 if (fileName) 609 { 610 if ((icon = [self _editorIconImageForFile:fileName])) 611 { 612 return icon; 613 } 614 fileExtension = [fileName pathExtension]; 615 } 616 else 617 { 618 categoryName = [self nameOfSelectedCategory]; 619 } 620 621/* PCLogError(self,@"{setFileIcon} file %@ category %@", 622 fileName, categoryName);*/ 623 624 if ([[self selectedFiles] count] > 1) 625 { 626 iconName = [[NSString alloc] initWithString:@"MultiFiles"]; 627 } 628 // Nothing or subproject name selected 629 else if ((!categoryName && !fileName) || 630 [fileName isEqualToString:[activeProject projectName]]) 631 { 632 iconName = [[NSString alloc] initWithString:@"FileProject"]; 633 } 634 else if ([categoryName isEqualToString: @"Classes"]) 635 { 636 iconName = [[NSString alloc] initWithString:@"classSuitcase"]; 637 } 638 else if ([categoryName isEqualToString: @"Headers"]) 639 { 640 iconName = [[NSString alloc] initWithString:@"headerSuitcase"]; 641 } 642 else if ([categoryName isEqualToString: @"Other Sources"]) 643 { 644 iconName = [[NSString alloc] initWithString:@"genericSuitcase"]; 645 } 646 else if ([categoryName isEqualToString: @"Interfaces"]) 647 { 648 iconName = [[NSString alloc] initWithString:@"nibSuitcase"]; 649 } 650 else if ([categoryName isEqualToString: @"Images"]) 651 { 652 iconName = [[NSString alloc] initWithString:@"iconSuitcase"]; 653 } 654 else if ([categoryName isEqualToString: @"Other Resources"]) 655 { 656 iconName = [[NSString alloc] initWithString:@"otherSuitcase"]; 657 } 658 else if ([categoryName isEqualToString: @"Subprojects"]) 659 { 660 iconName = [[NSString alloc] initWithString:@"subprojectSuitcase"]; 661 } 662 else if ([categoryName isEqualToString: @"Documentation"]) 663 { 664 iconName = [[NSString alloc] initWithString:@"helpSuitcase"]; 665 } 666 else if ([categoryName isEqualToString: @"Supporting Files"]) 667 { 668 iconName = [[NSString alloc] initWithString:@"genericSuitcase"]; 669 } 670 else if ([categoryName isEqualToString: @"Libraries"]) 671 { 672 iconName = [[NSString alloc] initWithString:@"librarySuitcase"]; 673 } 674 else if ([categoryName isEqualToString: @"Non Project Files"]) 675 { 676 iconName = [[NSString alloc] initWithString:@"projectSuitcase"]; 677 } 678 679 if (iconName != nil) 680 { 681 icon = IMAGE(iconName); 682 RELEASE(iconName); 683 } 684 else 685 { 686 icon = [[NSWorkspace sharedWorkspace] iconForFile:fileName]; 687 } 688 689 return icon; 690} 691 692- (NSString *)fileNameIconTitle 693{ 694 NSString *categoryName = [self nameOfSelectedCategory]; 695 NSString *fileName = [self nameOfSelectedFile]; 696 int filesCount = [[self selectedFiles] count]; 697 698 if (filesCount > 1) 699 { 700 return [NSString stringWithFormat:@"%i files", filesCount]; 701 } 702 else if (fileName) 703 { 704 return fileName; 705 } 706 else if (categoryName) 707 { 708 return categoryName; 709 } 710 711 return PCFileNameFieldNoFiles; 712} 713 714- (NSString *)fileNameIconPath 715{ 716 NSString *fileName = [self nameOfSelectedFile]; 717 NSString *category = [self nameOfSelectedCategory]; 718 719 return [project pathForFile:fileName 720 forKey:[project keyForCategory:category]]; 721} 722 723- (BOOL)canPerformDraggingOf:(NSArray *)paths 724{ 725 NSString *category = [self nameOfSelectedCategory]; 726 NSString *categoryKey = [project keyForCategory:category]; 727 NSArray *fileTypes = [project fileTypesForCategoryKey:categoryKey]; 728 NSEnumerator *e = [paths objectEnumerator]; 729 NSString *s; 730 731 NSLog(@"PCBrowser: canPerformDraggingOf -> %@", category); 732 733 if (!category || ([self nameOfSelectedFile] != nil)) 734 { 735 return NO; 736 } 737 738 if (![project isEditableCategory:category]) 739 { 740 return NO; 741 } 742 743 // Check if we can accept files of such types 744 while ((s = [e nextObject])) 745 { 746 if (![fileTypes containsObject:[s pathExtension]]) 747 { 748 return NO; 749 } 750 } 751 752 return YES; 753} 754 755- (BOOL)prepareForDraggingOf:(NSArray *)paths 756{ 757 return YES; 758} 759 760- (BOOL)performDraggingOf:(NSArray *)paths 761{ 762 NSString *category = [self nameOfSelectedCategory]; 763 NSString *categoryKey = [project keyForCategory:category]; 764 NSEnumerator *pathsEnum = [paths objectEnumerator]; 765 NSString *file = nil; 766 767 while ((file = [[pathsEnum nextObject] lastPathComponent])) 768 { 769 if (![project doesAcceptFile:file forKey:categoryKey]) 770 { 771 return NO; 772 } 773 } 774 775 return [project addAndCopyFiles:paths forKey:categoryKey]; 776} 777 778@end 779