// -*- mode:objc -*-
// $Id: iTermController.m,v 1.74 2008/09/13 00:07:17 delx Exp $
/*
** iTermController.m
**
** Copyright (c) 2002, 2003
**
** Author: Fabian, Ujwal S. Setlur
** Initial code by Kiichi Kusama
**
** Project: iTerm
**
** Description: Implements the main application delegate and handles the addressbook functions.
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
// Debug option
#define DEBUG_ALLOC 0
#define DEBUG_METHOD_TRACE 0
#import <iTerm/iTermController.h>
#import <iTerm/PreferencePanel.h>
#import <iTerm/PseudoTerminal.h>
#import <iTerm/PTYSession.h>
#import <iTerm/VT100Screen.h>
#import <iTerm/NSStringITerm.h>
#import <iTerm/ITAddressBookMgr.h>
#import <iTerm/Tree.h>
#import <iTerm/ITConfigPanelController.h>
#import <iTerm/iTermGrowlDelegate.h>
#import <iTermProfileWindowController.h>
#import <iTermBookmarkController.h>
static NSString* APPLICATION_SUPPORT_DIRECTORY = @"~/Library/Application Support";
static NSString *SUPPORT_DIRECTORY = @"~/Library/Application Support/iTerm";
static NSString *SCRIPT_DIRECTORY = @"~/Library/Application Support/iTerm/Scripts";
// Comparator for sorting encodings
static int _compareEncodingByLocalizedName(id a, id b, void *unused)
{
NSString *sa = [NSString localizedNameOfStringEncoding: [a unsignedIntValue]];
NSString *sb = [NSString localizedNameOfStringEncoding: [b unsignedIntValue]];
return [sa caseInsensitiveCompare: sb];
}
@implementation iTermController
+ (iTermController*)sharedInstance;
{
static iTermController* shared = nil;
if (!shared)
shared = [[iTermController alloc] init];
return shared;
}
// init
- (id) init
{
#if DEBUG_ALLOC
NSLog(@"%s(%d):-[iTermController init]",
__FILE__, __LINE__);
#endif
self = [super init];
// create the iTerm directory if it does not exist
NSFileManager *fileManager = [NSFileManager defaultManager];
// create the "~/Library/Application Support" directory if it does not exist
if([fileManager fileExistsAtPath: [APPLICATION_SUPPORT_DIRECTORY stringByExpandingTildeInPath]] == NO)
[fileManager createDirectoryAtPath: [APPLICATION_SUPPORT_DIRECTORY stringByExpandingTildeInPath] attributes: nil];
if([fileManager fileExistsAtPath: [SUPPORT_DIRECTORY stringByExpandingTildeInPath]] == NO)
[fileManager createDirectoryAtPath: [SUPPORT_DIRECTORY stringByExpandingTildeInPath] attributes: nil];
terminalWindows = [[NSMutableArray alloc] init];
// Activate Growl
/*
* Need to add routine in iTerm prefs for Growl support and
* PLIST check here.
*/
gd = [iTermGrowlDelegate sharedInstance];
return (self);
}
- (void) dealloc
{
#if DEBUG_ALLOC
NSLog(@"%s(%d):-[iTermController dealloc]",
__FILE__, __LINE__);
#endif
// Release the GrowlDelegate
if( gd )
[gd release];
[terminalWindows removeAllObjects];
[terminalWindows release];
[super dealloc];
}
// Action methods
- (IBAction)newWindow:(id)sender
{
[self launchBookmark:nil inTerminal: nil];
}
- (void) newSessionInTabAtIndex: (id) sender
{
[self launchBookmark:[sender representedObject] inTerminal:FRONT];
}
- (void)newSessionInWindowAtIndex: (id) sender
{
[self launchBookmark:[sender representedObject] inTerminal:nil];
}
// Open all childs within a given window
- (PseudoTerminal *) newSessionsInWindow:(PseudoTerminal *) terminal forNode:(TreeNode*)theNode
{
NSEnumerator *entryEnumerator;
NSDictionary *dataDict;
TreeNode *childNode;
PseudoTerminal *term =terminal;
entryEnumerator = [[theNode children] objectEnumerator];
while ((childNode = [entryEnumerator nextObject]))
{
dataDict = [childNode nodeData];
if([childNode isGroup])
{
[self newSessionsInWindow:terminal forNode:childNode];
}
else
{
if (!term) {
term = [[PseudoTerminal alloc] init];
[term initWindowWithAddressbook: [childNode nodeData]];
[self addInTerminals: term];
[term release];
}
[self launchBookmark:[childNode nodeData] inTerminal:term];
}
}
return term;
}
- (void) newSessionsInWindow: (id) sender
{
[self newSessionsInWindow:FRONT forNode:[sender representedObject]];
}
- (void) newSessionsInNewWindow: (id) sender
{
[self newSessionsInWindow:nil forNode:[sender representedObject]];
}
// meant for action for menu items that have a submenu
- (void) noAction: (id) sender
{
}
- (IBAction)newSession:(id)sender
{
[self launchBookmark:nil inTerminal: FRONT];
}
// navigation
- (IBAction) previousTerminal: (id) sender
{
unsigned int currentIndex;
BOOL looped = NO;
currentIndex = [[self terminals] indexOfObject: FRONT];
if(FRONT == nil || currentIndex == NSNotFound)
{
NSBeep();
return;
}
// get the previous terminal
do {
if(currentIndex == 0) {
if (looped) return;
currentIndex = [[self terminals] count] - 1;
looped = YES;
}
else
currentIndex--;
} while ([[[[self terminals] objectAtIndex: currentIndex] window] isMiniaturized]);
// make sure that terminal's window active
[[[[self terminals] objectAtIndex: currentIndex] window] makeKeyAndOrderFront: self];
}
- (IBAction)nextTerminal: (id) sender
{
unsigned int currentIndex;
BOOL looped = NO;
currentIndex = [[self terminals] indexOfObject: FRONT];
if(FRONT == nil || currentIndex == NSNotFound)
{
NSBeep();
return;
}
// get the next terminal
do {
if(currentIndex == [[self terminals] count] - 1) {
if (looped) return;
currentIndex = 0;
looped = YES;
}
else
currentIndex++;
} while ([[[[self terminals] objectAtIndex: currentIndex] window] isMiniaturized]);
// make sure that terminal's window active
[[[[self terminals] objectAtIndex: currentIndex] window] makeKeyAndOrderFront: self];
}
- (PseudoTerminal *) currentTerminal
{
return (FRONT);
}
- (void) terminalWillClose: (PseudoTerminal *) theTerminalWindow
{
if(FRONT == theTerminalWindow)
[self setCurrentTerminal: nil];
if(theTerminalWindow)
[self removeFromTerminalsAtIndex: [terminalWindows indexOfObject: theTerminalWindow]];
}
// Build sorted list of encodings
- (NSArray *) sortedEncodingList
{
NSStringEncoding const *p;
NSMutableArray *tmp = [NSMutableArray array];
for (p = [NSString availableStringEncodings]; *p; ++p)
[tmp addObject:[NSNumber numberWithUnsignedInt:*p]];
[tmp sortUsingFunction: _compareEncodingByLocalizedName context:NULL];
return (tmp);
}
- (void) alternativeMenu: (NSMenu *)aMenu forNode: (TreeNode *) theNode target: (id) aTarget withShortcuts: (BOOL) withShortcuts
{
NSMenu *subMenu;
NSMenuItem *aMenuItem;
NSEnumerator *entryEnumerator;
NSDictionary *dataDict;
TreeNode *childNode;
NSString *shortcut;
unsigned int modifierMask = NSCommandKeyMask | NSControlKeyMask;
int count = 0;
entryEnumerator = [[theNode children] objectEnumerator];
while ((childNode = [entryEnumerator nextObject]))
{
count ++;
dataDict = [childNode nodeData];
aMenuItem = [[[NSMenuItem alloc] initWithTitle: [dataDict objectForKey: KEY_NAME] action:@selector(newSessionInTabAtIndex:) keyEquivalent:@""] autorelease];
if([childNode isGroup])
{
subMenu = [[[NSMenu alloc] init] autorelease];
[self alternativeMenu: subMenu forNode: childNode target: aTarget withShortcuts: withShortcuts];
[aMenuItem setSubmenu: subMenu];
[aMenuItem setAction:@selector(noAction:)];
[aMenuItem setTarget: self];
[aMenu addItem: aMenuItem];
}
else
{
if(withShortcuts)
{
if ([dataDict objectForKey: KEY_SHORTCUT] != nil)
{
shortcut=[dataDict objectForKey: KEY_SHORTCUT];
shortcut = [shortcut lowercaseString];
[aMenuItem setKeyEquivalent: shortcut];
}
}
[aMenuItem setKeyEquivalentModifierMask: modifierMask];
[aMenuItem setRepresentedObject: dataDict];
[aMenuItem setTarget: aTarget];
[aMenu addItem: aMenuItem];
aMenuItem = [[aMenuItem copy] autorelease];
[aMenuItem setKeyEquivalentModifierMask: modifierMask | NSAlternateKeyMask];
[aMenuItem setAlternate:YES];
[aMenuItem setAction: @selector(newSessionInWindowAtIndex:)];
[aMenuItem setTarget: self];
[aMenu addItem: aMenuItem];
}
}
if (count>1) {
[aMenu addItem:[NSMenuItem separatorItem]];
aMenuItem = [[[NSMenuItem alloc] initWithTitle: NSLocalizedStringFromTableInBundle(@"Open All",@"iTerm", [NSBundle bundleForClass: [iTermController class]], @"Context Menu") action:@selector(newSessionsInWindow:) keyEquivalent:@""] autorelease];
[aMenuItem setKeyEquivalentModifierMask: modifierMask];
[aMenuItem setRepresentedObject: theNode];
[aMenuItem setTarget: self];
[aMenu addItem: aMenuItem];
aMenuItem = [[aMenuItem copy] autorelease];
[aMenuItem setKeyEquivalentModifierMask: modifierMask | NSAlternateKeyMask];
[aMenuItem setAlternate:YES];
[aMenuItem setAction: @selector(newSessionsInNewWindow:)];
[aMenuItem setTarget: self];
[aMenu addItem: aMenuItem];
}
}
// Executes an addressbook command in new window or tab
- (void) launchBookmark: (NSDictionary *) bookmarkData inTerminal: (PseudoTerminal *) theTerm
{
PseudoTerminal *term;
NSDictionary *aDict;
aDict = bookmarkData;
if(aDict == nil)
aDict = [[ITAddressBookMgr sharedInstance] defaultBookmarkData];
// Where do we execute this command?
if(theTerm == nil)
{
term = [[PseudoTerminal alloc] init];
[term initWindowWithAddressbook: aDict];
[self addInTerminals: term];
[term release];
}
else
term = theTerm;
[term addNewSession: aDict];
}
- (void) launchBookmark: (NSDictionary *) bookmarkData inTerminal: (PseudoTerminal *) theTerm withCommand: (NSString *)command
{
PseudoTerminal *term;
NSDictionary *aDict;
aDict = bookmarkData;
if(aDict == nil)
aDict = [[ITAddressBookMgr sharedInstance] defaultBookmarkData];
// Where do we execute this command?
if(theTerm == nil)
{
term = [[PseudoTerminal alloc] init];
[term initWindowWithAddressbook: aDict];
[self addInTerminals: term];
[term release];
}
else
term = theTerm;
[term addNewSession: aDict withCommand: command];
}
- (void) launchBookmark: (NSDictionary *) bookmarkData inTerminal: (PseudoTerminal *) theTerm withURL: (NSString *)url
{
PseudoTerminal *term;
NSDictionary *aDict;
aDict = bookmarkData;
if(aDict == nil)
aDict = [[ITAddressBookMgr sharedInstance] defaultBookmarkData];
// Where do we execute this command?
if(theTerm == nil)
{
term = [[PseudoTerminal alloc] init];
[term initWindowWithAddressbook: aDict];
[self addInTerminals: term];
[term release];
}
else
term = theTerm;
[term addNewSession: aDict withURL: url];
}
- (void) launchScript: (id) sender
{
NSString *fullPath = [NSString stringWithFormat: @"%@/%@", [SCRIPT_DIRECTORY stringByExpandingTildeInPath], [sender title]];
if ([[[sender title] pathExtension] isEqualToString: @"scpt"]) {
NSAppleScript *script;
NSDictionary *errorInfo = [NSDictionary dictionary];
NSURL *aURL = [NSURL fileURLWithPath: fullPath];
// Make sure our script suite registry is loaded
[NSScriptSuiteRegistry sharedScriptSuiteRegistry];
script = [[NSAppleScript alloc] initWithContentsOfURL: aURL error: &errorInfo];
[script executeAndReturnError: &errorInfo];
[script release];
}
else {
[[NSWorkspace sharedWorkspace] launchApplication:fullPath];
}
}
- (PTYTextView *) frontTextView
{
return ([[FRONT currentSession] TEXTVIEW]);
}
@end
// keys for to-many relationships:
NSString *terminalsKey = @"terminals";
// Scripting support
@implementation iTermController (KeyValueCoding)
- (BOOL)application:(NSApplication *)sender delegateHandlesKey:(NSString *)key
{
BOOL ret;
// NSLog(@"key = %@", key);
ret = [key isEqualToString:@"terminals"] || [key isEqualToString:@"currentTerminal"];
return (ret);
}
// accessors for to-many relationships:
-(NSArray*)terminals
{
// NSLog(@"iTerm: -terminals");
return (terminalWindows);
}
-(void)setTerminals: (NSArray*)terminals
{
// no-op
}
// accessors for to-many relationships:
// (See NSScriptKeyValueCoding.h)
-(id)valueInTerminalsAtIndex:(unsigned)index
{
//NSLog(@"iTerm: valueInTerminalsAtIndex %d: %@", index, [terminalWindows objectAtIndex: index]);
return ([terminalWindows objectAtIndex: index]);
}
- (void) setCurrentTerminal: (PseudoTerminal *) thePseudoTerminal
{
FRONT = thePseudoTerminal;
// make sure this window is the key window
if([thePseudoTerminal windowInited] && [[thePseudoTerminal window] isKeyWindow] == NO)
[[thePseudoTerminal window] makeKeyAndOrderFront: self];
// Post a notification
[[NSNotificationCenter defaultCenter] postNotificationName: @"iTermWindowBecameKey" object: thePseudoTerminal userInfo: nil];
}
-(void)replaceInTerminals:(PseudoTerminal *)object atIndex:(unsigned)index
{
// NSLog(@"iTerm: replaceInTerminals 0x%x atIndex %d", object, index);
[terminalWindows replaceObjectAtIndex: index withObject: object];
}
- (void) addInTerminals: (PseudoTerminal *) object
{
// NSLog(@"iTerm: addInTerminals 0x%x", object);
[self insertInTerminals: object atIndex: [terminalWindows count]];
}
- (void) insertInTerminals: (PseudoTerminal *) object
{
// NSLog(@"iTerm: insertInTerminals 0x%x", object);
[self insertInTerminals: object atIndex: [terminalWindows count]];
}
-(void)insertInTerminals:(PseudoTerminal *)object atIndex:(unsigned)index
{
if([terminalWindows containsObject: object] == YES)
return;
[terminalWindows insertObject: object atIndex: index];
// make sure we have a window
[object initWindowWithAddressbook:NULL];
}
-(void)removeFromTerminalsAtIndex:(unsigned)index
{
// NSLog(@"iTerm: removeFromTerminalsAtInde %d", index);
[terminalWindows removeObjectAtIndex: index];
if([terminalWindows count] == 0)
[ITConfigPanelController close];
}
// a class method to provide the keys for KVC:
- (NSArray*)kvcKeys
{
static NSArray *_kvcKeys = nil;
if( nil == _kvcKeys ){
_kvcKeys = [[NSArray alloc] initWithObjects:
terminalsKey, nil ];
}
return _kvcKeys;
}
@end
@implementation iTermController (Private)
@end