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];
    if( [NSApp runModalForWindow: _clonePanel] == NSOKButton ) {
        NSURL *srcURL = _cloneSrcURLField.objectValue;
        NSURL *dstURL = _cloneDstURLField.objectValue;
        if( ! srcURL || ! dstURL ) {
            NSBeep();
            return;
        }
        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];
            return;
        }
        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,
                                         dstURL.absoluteString);
    }
    [_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) 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 runModalForDirectory: nil file: nil] == NSOKButton ) {
        NSString *path = [panel filename];
		[[NSFileManager defaultManager] createDirectoryAtPath: path withIntermediateDirectories: YES attributes: nil error:NULL];
        [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 runModalForDirectory: nil file: nil types: nil] == NSOKButton ) {
        NSString *path = [[panel filenames] 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";
    int selectedItemIndex = [editorIDs indexOfObject:selectedEditorID];
    if (selectedItemIndex == NSNotFound)
        selectedItemIndex = 0;
    [_textEditorPreferenceButton selectItemAtIndex:selectedItemIndex];
    [[_textEditorPreferenceButton window] makeKeyAndOrderFront:self];
}

- (IBAction) selectTextEditor:(id)sender {
    int 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.