Source

Murky / Source / MercurialApp.m

//
//  MercurialApp.m
//  Murky
//
//  Copyright 2008-2009 Jens Alfke. All rights reserved.
//

#import "MercurialApp.h"
#import "RepoController.h"
#import "ProjectsController.h"
#import "HgRepository.h"
#import "HgOperation.h"
#import "HgTempDir.h"
#import "URLFormatter.h"
#import "ExceptionUtils.h"


/** User defaults: */
#define kPrefOpenRepositories   @"OpenRepositories"
#define kPrefRecentRepositories @"RecentRepositories"


int main(int argc, const char** argv)
{
    RunTestCases(argc,argv);
    return NSApplicationMain(argc, argv);
}




MercurialApp *gMercurialApp;


@implementation MercurialApp


#pragma mark -
#pragma mark OPENING REPOSITORIES:


- (void) rememberOpenRepositories
{
    NSMutableArray *openRepositories = [NSMutableArray array];
    for(RepoController *controller in [RepoController allRepoControllers]) {
        [openRepositories addObject: controller.directory];
    }
    
    [[NSUserDefaults standardUserDefaults] setObject: openRepositories forKey: kPrefOpenRepositories];
}


- (void) _rememberController: (RepoController*)controller
{
    // Add to open-repositories user default:
    [self rememberOpenRepositories];
    
    // Add to the Recent Repositories submenu:
    NSURL *url = [NSURL fileURLWithPath: controller.directory];
    [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL: url];
    [[NSUserDefaults standardUserDefaults] synchronize];
}


- (BOOL) openRepository: (NSString*)path remember: (BOOL)remember
{
    NSError *error = nil;
    RepoController *controller = [RepoController repoControllerWithDirectory: path error: &error];
    if( ! controller ) {
        if( error )
            [NSApp presentError: error];
        return NO;
    } else if( controller.window.isVisible ) {
        // Was already open; just make it activate:
        [[controller window] makeKeyAndOrderFront: self];
    } else {
        // Must be new; open window and remember it:
        [controller showWindow: self];
        if( remember ) 
            [self _rememberController: controller];
    }
    return YES;
}

#pragma mark -
#pragma mark CLONING:


- (IBAction) runClonePanel: (id)sender
{
    [_clonePanel center];

    BOOL terminate = YES;
    do {
        if( [NSApp runModalForWindow: _clonePanel] == NSOKButton ) {
            NSURL *srcURL = _cloneSrcURLField.objectValue;
            NSURL *dstURL = _cloneDstURLField.objectValue;
            if( ! srcURL || ! dstURL ) {
                NSBeep();
                NSRunInformationalAlertPanel(NSLocalizedString(@"Missing required parameters", 
                                                               @"Title of pre-clone failure alert"),
                                             NSLocalizedString(@"You must select both a source and a destination", 
                                                               @"Body of pre-clone failure alert"),
                                             nil, nil, nil);
                // operation not completed because one of the URLs (or both) is invalid
                terminate = NO;
                continue;
            } else {
                Log(@"Clone from <%@> to <%@>",srcURL,dstURL);
                NSError *error = nil;
                HgOperation *op = [HgRepository cloneRevision: nil fromURL: srcURL toURL: dstURL
                                                      options: kHgUpdateAfterwards];
                if( ! [op run: &error] ) {
                    [NSApp presentError: error];
                    // operation not completed because of error
                    terminate = NO;
                    continue;
                }
                if( dstURL.isFileURL )
                    [self openRepository: dstURL.path remember: YES];
                else
                    NSRunInformationalAlertPanel(NSLocalizedString(@"Cloned Repository", 
                                                                   @"Title of post-clone success alert"),
                                                 NSLocalizedString(@"Successfully created a cloned repository at \n\n%@", 
                                                                   @"Body of post-clone success alert"),
                                                 nil, nil, nil);
                
                // ok button was pressed and clone was performed
                terminate = YES;
                continue;
            }
        } else {
            // cancel button was pressed
            terminate = YES;
            continue;
        }
    } while ( ! terminate );
    
    [_clonePanel orderOut: self];
}


- (IBAction) dismissClonePanel: (id)sender
{
    [NSApp stopModalWithCode: [sender tag]];
}

- (IBAction) beginClonePanelSrcFilePicker: (id)sender
{
    [_clonePanel makeFirstResponder: _cloneSrcURLField];
    [URLFormatter beginFilePickerFor: _cloneSrcURLField];
}

- (IBAction) beginClonePanelDstFilePicker: (id)sender
{
    [_clonePanel makeFirstResponder: _cloneDstURLField];
    [URLFormatter beginNewFilePickerFor: _cloneDstURLField];
}


#pragma mark -
#pragma mark APPLICATION DELEGATE STUFF:


- (void) awakeFromNib
{
    gMercurialApp = self;
}

- (void)applicationWillFinishLaunching:(NSNotification *)notification
{
    // initialize the field values for the ( ! srcURL || ! dstURL ) check in runClonePanel to work properly
    _cloneSrcURLField.objectValue = nil;
    _cloneDstURLField.objectValue = nil;
}

- (void) applicationDidFinishLaunching: (NSNotification*)n
{
    // First, make sure Mercurial is installed:
    HgOperation *op = [[HgOperation alloc] initWithDirectory: NSHomeDirectory() command: @"--version", nil];
    NSError *error;
    if( ! [op run: &error] ) {
        NSRunCriticalAlertPanel(NSLocalizedString(@"Can't call Mercurial", 
                                                  @"Title of fatal error alert"),
                                NSLocalizedString(@"Unable to find or run the 'hg' command.\n\n%@",
                                                  @"Body of fatal error alert"),
                                NSLocalizedString(@"Sorry", @"Button in fatal error alert"), nil, nil,
                                error.localizedDescription);
        [NSApp terminate: self];
        return;
    }
    
    [ProjectsController applicationLaunched];
    
    // Re-open repositories that were open when I last quit:
    for( NSString *path in [[NSUserDefaults standardUserDefaults] objectForKey: kPrefOpenRepositories]) {
        @try{
            [self openRepository: path remember: NO];
        }catchAndReport(@"Exception re-opening recent repository %@", path)
    }    
}


/** The "New Local Repository" menu command is wired to this */
- (IBAction) runNewPanel: (id)sender
{
    NSSavePanel *panel = [NSSavePanel savePanel];
    if( [panel runModal] == NSOKButton ) {
        NSString *path = [panel.URL path];
        [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
        [self openRepository:path remember:YES];
    
    }

}

/** The "Open..." menu command is wired to this */
- (IBAction) runOpenPanel: (id)sender
{
    NSOpenPanel *panel = [NSOpenPanel openPanel];
    panel.canChooseFiles = NO;
    panel.canChooseDirectories = YES;
    panel.allowsMultipleSelection = NO;
    if( [panel runModal] == NSOKButton && [panel.URLs count] > 0 ) {
        NSString *path = [panel.URLs objectAtIndex: 0];
        [self openRepository: path remember: YES];
    }
}


/** Called when app is sent an 'open' AppleEvent, e.g. when file is dropped on it */
- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename
{
    return [self openRepository: filename remember: YES];
}

- (void) applicationWillTerminate: (NSNotification*)n
{
    [self rememberOpenRepositories];
}


#pragma mark -
#pragma mark ACTIONS:


- (IBAction) showProjectsWindow: (id)sender
{
    [[ProjectsController sharedInstance] showWindow: sender];
}

- (IBAction) openHelpWebPage: (id)sender
{
    NSString* kPages[] = {
        @"http://www.selenic.com/mercurial/wiki/",
        @"http://www.selenic.com/mercurial/wiki/UnderstandingMercurial",
        @"http://hgbook.red-bean.com/read/",
        @"http://www.selenic.com/mercurial/wiki/FAQ",
        @"http://bitbucket.org/help/Home"
    };
    NSURL *url = [NSURL URLWithString: kPages[[sender tag]]];
    if (![[NSWorkspace sharedWorkspace] openURL: url])
        NSBeep();
}

#pragma mark -
#pragma mark PREFERENCES:


- (NSArray *)supportedTextEditors {
    static NSArray *s_supportedTextEditors = nil;
    if (!s_supportedTextEditors) {
        NSString *path = [[NSBundle mainBundle] pathForResource: @"TextEditors" ofType: @"plist"];
        s_supportedTextEditors = [NSArray arrayWithContentsOfFile: path];
        Assert(s_supportedTextEditors);
    }
    return s_supportedTextEditors;
}

- (IBAction) showPreferences:(id)sender {
    // update popup
    [_textEditorPreferenceButton removeAllItems];
    [_textEditorPreferenceButton addItemsWithTitles:[[self supportedTextEditors] valueForKeyPath:@"@unionOfObjects.editorName"]];
    [_textEditorPreferenceButton setAutoenablesItems:NO];
    
    NSArray *editorIDs = [[self supportedTextEditors] valueForKeyPath:@"@unionOfObjects.editorID"];
    for (id item in editorIDs) {
        NSString *path = [[NSWorkspace sharedWorkspace] absolutePathForAppBundleWithIdentifier: item];
        NSMenuItem *menuItem = [_textEditorPreferenceButton itemAtIndex:[editorIDs indexOfObject:item]];
        NSImage *icon;
        if (path) {
            icon = [[NSWorkspace sharedWorkspace] iconForFile: path];
        } else {
            icon = [[NSWorkspace sharedWorkspace] iconForFileType: NSFileTypeForHFSTypeCode(kGenericApplicationIcon)];
            [menuItem setEnabled:NO];
        }
        [icon setSize:NSMakeSize(16, 16)];
        [menuItem setImage: icon];
    }
    
    NSString *selectedEditorID = [[NSUserDefaults standardUserDefaults] stringForKey:kPrefPreferredTextEditorID];
    if (!selectedEditorID)
        selectedEditorID = @"com.apple.TextEdit";
    NSInteger selectedItemIndex = [editorIDs indexOfObject:selectedEditorID];
    if (selectedItemIndex == NSNotFound)
        selectedItemIndex = 0;
    [_textEditorPreferenceButton selectItemAtIndex:selectedItemIndex];
    [[_textEditorPreferenceButton window] makeKeyAndOrderFront:self];
}

- (IBAction) selectTextEditor:(id)sender {
    NSInteger selectedIndex = [_textEditorPreferenceButton indexOfSelectedItem];
    NSString *editorID = [[[self supportedTextEditors] valueForKeyPath:@"@unionOfObjects.editorID"] objectAtIndex:selectedIndex];
    [[NSUserDefaults standardUserDefaults] setObject: editorID
                                              forKey: kPrefPreferredTextEditorID];
}


@end
//
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.