// // MainWindowController.m // XcodeCapp // // Created by Alexandre Wilhelm on 5/20/15. // Copyright (c) 2015 cappuccino-project. All rights reserved. // #import "NSMutableArray+moveIndexes.h" #import "XCCMainController.h" #import "XCCCappuccinoProject.h" #import "XCCCappuccinoProjectController.h" #import "XCCCappuccinoProjectControllerDataView.h" #import "XCCUserDefaults.h" #import "XCCOperationsViewController.h" #import "XCCErrorsViewController.h" #import "XCCSettingsViewController.h" #import "XCCWelcomeView.h" @implementation XCCMainController #pragma mark - Initialization - (void)windowDidLoad { [self showWindow:self]; self->welcomeViewMask.mainXcodeCappController = self; [self->welcomeViewMask showLoading:YES]; [self _showWelcomeView:YES]; [self _showMaskingView:YES]; NSTabViewItem *itemConfiguration = [[NSTabViewItem alloc] initWithIdentifier:@"configuration"]; [itemConfiguration setView:self.settingsViewController.view]; [self->tabViewProject addTabViewItem:itemConfiguration]; NSTabViewItem *itemErrors = [[NSTabViewItem alloc] initWithIdentifier:@"errors"]; [itemErrors setView:self.errorsViewController.view]; [self->tabViewProject addTabViewItem:itemErrors]; NSTabViewItem *itemOperations = [[NSTabViewItem alloc] initWithIdentifier:@"operations"]; [itemOperations setView:self.operationsViewController.view]; [self->tabViewProject addTabViewItem:itemOperations]; [self _setTextColor:[NSColor controlTextColor] forButton:self->buttonSelectConfigurationTab]; [self _setTextColor:[NSColor controlTextColor] forButton:self->buttonSelectErrorsTab]; [self _setTextColor:[NSColor controlTextColor] forButton:self->buttonSelectOperationsTab]; [self updateSelectedTab:self->buttonSelectConfigurationTab]; [self->projectTableView registerForDraggedTypes:@[@"projects", NSFilenamesPboardType]]; [self->projectTableView setAllowsEmptySelection:YES]; [self->welcomeViewMask showLoading:NO]; [NSUserNotificationCenter defaultUserNotificationCenter].delegate = self; [self _restoreManagedProjects]; [self _restoreLastSelectedProject]; } - (void)_setTextColor:(NSColor *)color forButton:(NSButton *)button { NSMutableParagraphStyle *paragraphStyle= [NSMutableParagraphStyle new]; [paragraphStyle setAlignment:NSTextAlignmentCenter]; NSDictionary *attrs = @{NSFontAttributeName: [NSFont systemFontOfSize:11], NSForegroundColorAttributeName: color, NSParagraphStyleAttributeName: paragraphStyle}; button.attributedTitle = [[NSMutableAttributedString alloc] initWithString:button.title attributes:attrs]; } #pragma mark - Private Utilities - (void)_showMaskingView:(BOOL)shouldShow { if (shouldShow) { if (self->maskingView.superview) return; self->projectViewContainer.hidden = YES; self->maskingView.frame = [[self->splitView subviews][1] bounds]; [[self->splitView subviews][1] addSubview:self->maskingView positioned:NSWindowAbove relativeTo:nil]; } else { if (!self->maskingView.superview) return; self->projectViewContainer.hidden = NO; [self->maskingView removeFromSuperview]; } } - (void)_showWelcomeView:(BOOL)shouldShow { if (shouldShow) { if (self->welcomeViewMask.superview) return; self->splitView.hidden = YES; self->welcomeViewMask.frame = [self->splitView.superview bounds]; [self->splitView.superview addSubview:self->welcomeViewMask positioned:NSWindowAbove relativeTo:nil]; } else { if (!self->welcomeViewMask.superview) return; self->splitView.hidden = NO; [self->welcomeViewMask removeFromSuperview]; } } - (void)_restoreLastSelectedProject { NSLog(@"Start : selecting last selected project"); NSString *lastSelectedProjectPath = [[NSUserDefaults standardUserDefaults] valueForKey:XCCUserDefaultsSelectedProjectPath]; [self _selectCappuccinoProjectControllerWithPath:lastSelectedProjectPath]; NSLog(@"Stop : selecting last selected project"); } - (void)_selectCappuccinoProjectControllerWithPath:(NSString*)aCappuccinoProjectPath { NSInteger indexToSelect = 0; if ([aCappuccinoProjectPath length]) { for (XCCCappuccinoProjectController *controller in self.cappuccinoProjectControllers) { if ([controller.cappuccinoProject.projectPath isEqualToString:aCappuccinoProjectPath]) { indexToSelect = [self.cappuccinoProjectControllers indexOfObject:controller]; break; } } [self->projectTableView selectRowIndexes:[NSIndexSet indexSetWithIndex:indexToSelect] byExtendingSelection:NO]; } } - (void)_restoreManagedProjects { NSLog(@"restore managed projects"); self.cappuccinoProjectControllers = [@[] mutableCopy]; NSArray *projectHistory = [[NSUserDefaults standardUserDefaults] arrayForKey:XCCUserDefaultsManagedProjects]; NSFileManager *fm = [NSFileManager defaultManager]; NSMutableArray *missingProjects = [@[] mutableCopy]; for (NSString *path in projectHistory) { NSLog(@"Checking previously managed project at path: %@", path); if (![fm fileExistsAtPath:path isDirectory:nil]) { [missingProjects addObject:path]; NSLog(@"Not found: project at path: %@", path); continue; } XCCCappuccinoProjectController *cappuccinoProjectController = [[XCCCappuccinoProjectController alloc] initWithPath:path controller:self]; [self.cappuccinoProjectControllers addObject:cappuccinoProjectController]; } [self _reloadProjectsList]; if (missingProjects.count) { NSString *informativeText = [NSString stringWithFormat:@"Some managed projects could not be found and have been removed:\n\n%@", [missingProjects componentsJoinedByString:@", "]]; NSAlert *alert = [[NSAlert alloc] init]; [alert setAlertStyle:NSAlertStyleCritical]; [alert setMessageText:@"Missing Projects"]; [alert setInformativeText:informativeText]; [alert runModal]; } [self _saveManagedProjectsToUserDefaults]; for (XCCCappuccinoProjectController *controller in self.cappuccinoProjectControllers) { if (controller.cappuccinoProject.previousSavedStatus) [controller switchProjectListeningStatus:self]; } NSLog(@"managed projects restored"); } - (void)_saveSelectedProject { NSString *path = self.currentCappuccinoProjectController.cappuccinoProject.projectPath; if (!path) path = @""; [[NSUserDefaults standardUserDefaults] setObject:path forKey:XCCUserDefaultsSelectedProjectPath]; } - (void)_saveManagedProjectsToUserDefaults { NSMutableArray *historyProjectPaths = [@[] mutableCopy]; for (XCCCappuccinoProjectController *controller in self.cappuccinoProjectControllers) [historyProjectPaths addObject:controller.cappuccinoProject.projectPath]; [[NSUserDefaults standardUserDefaults] setObject:historyProjectPaths forKey:XCCUserDefaultsManagedProjects]; } - (void)_reloadProjectsList { [self->projectTableView reloadData]; if (self.cappuccinoProjectControllers.count == 0) [self _showWelcomeView:YES]; else [self _showWelcomeView:NO]; } #pragma mark - Public Utilities - (void)manageCappuccinoProjectControllerForPath:(NSString *)path { if ([[self.cappuccinoProjectControllers filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"cappuccinoProject.projectPath == %@", path]] count]) { NSAlert *alert = [[NSAlert alloc] init]; [alert setAlertStyle:NSAlertStyleCritical]; [alert setMessageText:@"This project is already managed."]; [alert setInformativeText:@"Please remove the other project or use the reset button."]; [alert runModal]; return; } XCCCappuccinoProjectController *controller = [[XCCCappuccinoProjectController alloc] initWithPath:path controller:self]; [self.cappuccinoProjectControllers addObject:controller]; NSInteger index = [self.cappuccinoProjectControllers indexOfObject:controller]; [self _reloadProjectsList]; [self->projectTableView selectRowIndexes:[NSIndexSet indexSetWithIndex:index] byExtendingSelection:NO]; [self->projectTableView scrollRowToVisible:index]; [self _saveManagedProjectsToUserDefaults]; [controller switchProjectListeningStatus:self]; } - (void)unmanageCappuccinoProjectController:(XCCCappuccinoProjectController*)aController { NSInteger selectedCappuccinoProject = [self.cappuccinoProjectControllers indexOfObject:aController]; if (selectedCappuccinoProject == -1) return; [self->projectTableView deselectRow:selectedCappuccinoProject]; [aController cleanUpBeforeDeletion]; [self.cappuccinoProjectControllers removeObjectAtIndex:selectedCappuccinoProject]; [self _reloadProjectsList]; [self _saveManagedProjectsToUserDefaults]; } - (void)notifyCappuccinoControllersApplicationIsClosing { [self.cappuccinoProjectControllers makeObjectsPerformSelector:@selector(applicationIsClosing)]; } - (void)reloadTotalNumberOfErrors { int totalErrors = 0; for (XCCCappuccinoProjectController *controller in self.cappuccinoProjectControllers) totalErrors += controller.errors.count; [self willChangeValueForKey:@"totalNumberOfErrors"]; self.totalNumberOfErrors = totalErrors; [self didChangeValueForKey:@"totalNumberOfErrors"]; if (self.totalNumberOfErrors) [[[NSApplication sharedApplication] dockTile] setBadgeLabel:@(self.totalNumberOfErrors).description]; else [[[NSApplication sharedApplication] dockTile] setBadgeLabel:nil]; } #pragma mark - Actions - (IBAction)cleanAllErrors:(id)aSender { [self.cappuccinoProjectControllers makeObjectsPerformSelector:@selector(cleanProjectErrors:) withObject:self]; } - (IBAction)addProject:(id)aSender { NSOpenPanel *openPanel = [NSOpenPanel openPanel]; openPanel.title = @"Add a new Cappuccino Project to XcodeCapp"; openPanel.canCreateDirectories = YES; openPanel.canChooseDirectories = YES; openPanel.canChooseFiles = NO; if ([openPanel runModal] != NSModalResponseOK) return; NSString *projectPath = [[openPanel.URLs[0] path] stringByStandardizingPath]; [self manageCappuccinoProjectControllerForPath:projectPath]; } - (IBAction)removeProject:(id)aSender { NSInteger selectedCappuccinoProject = [self->projectTableView selectedRow]; if (selectedCappuccinoProject == -1) return; [self unmanageCappuccinoProjectController:(self.cappuccinoProjectControllers)[selectedCappuccinoProject]]; } - (IBAction)updateSelectedTab:(NSButton *)sender { self->buttonSelectConfigurationTab.state = NSOffState; self->buttonSelectErrorsTab.state = NSOffState; self->buttonSelectOperationsTab.state = NSOffState; [self _setTextColor:[NSColor controlTextColor] forButton:self->buttonSelectConfigurationTab]; [self _setTextColor:[NSColor controlTextColor] forButton:self->buttonSelectErrorsTab]; [self _setTextColor:[NSColor controlTextColor] forButton:self->buttonSelectOperationsTab]; sender.state = NSOnState; [self _setTextColor:[NSColor colorWithCalibratedRed:107.0/255.0 green:148.0/255.0 blue:236.0/255.0 alpha:1.0] forButton:sender]; if (sender == self->buttonSelectConfigurationTab) { [_operationsViewController setSelected:NO]; [_errorsViewController setSelected:NO]; [self->tabViewProject selectTabViewItemAtIndex:0]; } else if (sender == self->buttonSelectErrorsTab) { [_operationsViewController setSelected:NO]; [_errorsViewController setSelected:YES]; [self->tabViewProject selectTabViewItemAtIndex:1]; [_errorsViewController reload]; } else if (sender == self->buttonSelectOperationsTab) { [_operationsViewController setSelected:YES]; [_errorsViewController setSelected:NO]; [self->tabViewProject selectTabViewItemAtIndex:2]; [_operationsViewController reload]; } } #pragma mark - NSUserNotificationCenter delegate - (void)userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification { NSDictionary *userInfo = [notification userInfo]; [center removeDeliveredNotification:notification]; [self _selectCappuccinoProjectControllerWithPath:userInfo[@"cappuccinoProjectPath"]]; [self updateSelectedTab:self->buttonSelectErrorsTab]; [self.errorsViewController selectItem:userInfo[@"sourcePath"]]; } #pragma mark - SplitView delegate - (CGFloat)splitView:(NSSplitView *)aSplitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex { return 300.0; } - (CGFloat)splitView:(NSSplitView *)aSplitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex { return 350.0; } - (void)splitView:(NSSplitView *)aSplitView resizeSubviewsWithOldSize:(NSSize)oldSize { [aSplitView adjustSubviews]; [aSplitView setPosition:((NSView *)splitView.subviews.firstObject).frame.size.width ofDividerAtIndex:0]; } #pragma mark - TableView DataSource - (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView { return [self.cappuccinoProjectControllers count]; } #pragma mark - TableView Delegates - (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row { XCCCappuccinoProjectControllerDataView *dataView = [tableView makeViewWithIdentifier:@"MainCell" owner:nil]; XCCCappuccinoProjectController *cappuccinoProjectController = (self.cappuccinoProjectControllers)[row]; dataView.controller = cappuccinoProjectController; return dataView; } - (void)tableViewSelectionDidChange:(NSNotification *)notification { NSInteger selectedIndex = [self->projectTableView selectedRow]; self.currentCappuccinoProjectController = (selectedIndex == -1) ? nil : (self.cappuccinoProjectControllers)[selectedIndex]; self.operationsViewController.cappuccinoProjectController = self.currentCappuccinoProjectController; self.errorsViewController.cappuccinoProjectController = self.currentCappuccinoProjectController; self.settingsViewController.cappuccinoProjectController = self.currentCappuccinoProjectController; [self.operationsViewController reload]; [self.errorsViewController reload]; [self.settingsViewController reload]; [self _showMaskingView:(selectedIndex == -1)]; [self _saveSelectedProject]; } - (BOOL)tableView:(NSTableView *)tableView writeRowsWithIndexes:(NSIndexSet *)rowsIndexes toPasteboard:(NSPasteboard*)pasteboard { NSData *data = [NSKeyedArchiver archivedDataWithRootObject:rowsIndexes]; [pasteboard declareTypes:@[@"projects"] owner:self]; [pasteboard setData:data forType:@"projects"]; return YES; } - (NSDragOperation)tableView:(NSTableView*)tableView validateDrop:(id )info proposedRow:(NSInteger)row proposedDropOperation:(NSTableViewDropOperation)operation { NSPasteboard *pboard = [info draggingPasteboard]; if ([pboard.types containsObject:(NSString *)NSFilenamesPboardType]) { NSArray *draggedFiles = [pboard propertyListForType:(NSString *)NSFilenamesPboardType]; for (NSString *file in draggedFiles) { BOOL isDir; [[NSFileManager defaultManager] fileExistsAtPath:file isDirectory:&isDir]; if (!isDir) return NSDragOperationNone; } return NSDragOperationCopy; } else if ([pboard.types containsObject:@"projects"]) { if (operation == NSTableViewDropOn) return NSDragOperationNone; return NSDragOperationMove; } else { return NSDragOperationNone; } } - (BOOL)tableView:(NSTableView *)tableView acceptDrop:(id )info row:(NSInteger)row dropOperation:(NSTableViewDropOperation)operation { NSPasteboard *pboard = [info draggingPasteboard]; if ([pboard.types containsObject:(NSString *)NSFilenamesPboardType]) { NSArray *draggedFolders = [pboard propertyListForType:(NSString *)NSFilenamesPboardType]; for (NSString *folder in draggedFolders) [self manageCappuccinoProjectControllerForPath:folder]; return YES; } else if ([pboard.types containsObject:@"projects"]) { NSData *rowData = [pboard dataForType:@"projects"]; NSIndexSet *rowIndexes = [NSKeyedUnarchiver unarchiveObjectWithData:rowData]; [self.cappuccinoProjectControllers moveIndexes:rowIndexes toIndex:row]; [self _reloadProjectsList]; [self _saveManagedProjectsToUserDefaults]; return YES; } else { return NO; } } @end