Files
textmate/Frameworks/BundleEditor/src/BundleEditor.mm
Allan Odgaard 9894969e67 Initial commit
2012-08-09 16:25:56 +02:00

734 lines
27 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#import "BundleEditor.h"
#import "PropertiesViewController.h"
#import "OakRot13Transformer.h"
#import "be_entry.h"
#import <OakFoundation/OakFoundation.h>
#import <OakFoundation/NSString Additions.h>
#import <OakFoundation/OakStringListTransformer.h>
#import <OakAppKit/NSAlert Additions.h>
#import <OakAppKit/NSImage Additions.h>
#import <OakAppKit/OakSound.h>
#import <OakAppKit/OakFileIconImage.h>
#import <OakTextView/OakDocumentView.h>
#import <plist/ascii.h>
#import <plist/delta.h>
#import <text/decode.h>
#import <cf/cf.h>
#import <ns/ns.h>
#import <oak/debug.h>
#import <oak/CocoaSTL.h>
OAK_DEBUG_VAR(BundleEditor);
static BundleEditor* SharedInstance;
@interface BundleEditor ()
- (void)didChangeBundleItems;
- (void)didChangeModifiedState;
@property (nonatomic, retain) PropertiesViewController* sharedPropertiesViewController;
@property (nonatomic, retain) PropertiesViewController* extraPropertiesViewController;
@property (nonatomic, retain) NSMutableDictionary* bundleItemProperties;
- (bundles::item_ptr const&)bundleItem;
- (void)setBundleItem:(bundles::item_ptr const&)aBundleItem;
@end
@interface NSBrowser (SnowLeopard)
- (void)setAutohidesScroller:(BOOL)flag;
@end
namespace
{
static bundles::kind_t const PlistItemKinds[] = { bundles::kItemTypeSettings, bundles::kItemTypeMacro, bundles::kItemTypeTheme };
static struct item_info_t { bundles::kind_t kind; std::string plist_key; std::string grammar; std::string file_type; std::string kind_string; NSString* view_controller; NSString* file; } item_infos[] =
{
{ bundles::kItemTypeBundle, "description", "text.html.basic", "tmBundle", "bundle", @"BundleProperties", @"Bundle" },
{ bundles::kItemTypeCommand, "command", NULL_STR, "tmCommand", "command", @"CommandProperties", @"Command" },
{ bundles::kItemTypeDragCommand, "command", NULL_STR, "tmDragCommand", "dragCommand", @"FileDropProperties", @"Drag Command" },
{ bundles::kItemTypeSnippet, "content", "text.tm-snippet", "tmSnippet", "snippet", @"SnippetProperties", @"Snippet" },
{ bundles::kItemTypeSettings, "settings", "source.plist.textmate.settings", "tmPreferences", "settings", nil, @"Settings" },
{ bundles::kItemTypeGrammar, NULL_STR, "source.plist.textmate.grammar", "tmLanguage", "grammar", @"GrammarProperties", @"Grammar" },
{ bundles::kItemTypeProxy, "content", "text.plain", "tmProxy", "proxy", nil, @"Proxy" },
{ bundles::kItemTypeTheme, "settings", "meta.plist", "tmTheme", "theme", @"ThemeProperties", @"Theme" },
{ bundles::kItemTypeMacro, "commands", "meta.plist", "tmMacro", "macro", @"MacroProperties", @"Macro" },
};
item_info_t const& info_for (bundles::kind_t kind)
{
iterate(it, item_infos)
{
if(it->kind == kind)
return *it;
}
static item_info_t dummy;
return dummy;
}
}
static NSMutableArray* wrap_array (std::vector<std::string> const& array, NSString* key)
{
NSMutableArray* res = [NSMutableArray array];
iterate(str, array)
[res addObject:[NSMutableDictionary dictionaryWithObject:[NSString stringWithCxxString:*str] forKey:key]];
return res;
}
static plist::array_t unwrap_array (NSArray* array, NSString* key)
{
plist::array_t res;
iterate(dict, array)
res.push_back(to_s((NSString*)[*dict objectForKey:key]));
return res;
}
namespace
{
struct expand_visitor_t : boost::static_visitor<void>
{
expand_visitor_t (std::map<std::string, std::string> const& variables) : _variables(variables) { }
template <typename T>
void operator() (T value) const { }
void operator() (std::string& str) const { str = format_string::expand(str, _variables); }
void operator() (plist::array_t& array) const { iterate(it, array) boost::apply_visitor(*this, *it); }
void operator() (plist::dictionary_t& dict) const { iterate(pair, dict) boost::apply_visitor(*this, pair->second); }
private:
std::map<std::string, std::string> const& _variables;
};
}
static be::entry_ptr parent_for_column (NSBrowser* aBrowser, NSInteger aColumn, be::entry_ptr entry)
{
for(size_t col = 0; col < aColumn; ++col)
{
NSInteger row = [aBrowser selectedRowInColumn:col];
if(row == -1)
{
fprintf(stderr, "*** abort\n");
return be::entry_ptr();
}
entry = entry->children()[row];
}
return entry;
}
@implementation BundleEditor
@synthesize sharedPropertiesViewController, extraPropertiesViewController, bundleItemProperties;
+ (BundleEditor*)sharedInstance
{
return SharedInstance ?: [[self new] autorelease];
}
- (id)init
{
if(SharedInstance)
{
[self release];
}
else if(self = SharedInstance = [[super initWithWindowNibName:@"BundleEditor"] retain])
{
D(DBF_BundleEditor, bug("\n"););
struct callback_t : bundles::callback_t
{
callback_t (BundleEditor* self) : self(self) { }
void bundles_did_change () { [self didChangeBundleItems]; }
private:
BundleEditor* self;
};
static callback_t cb(self);
bundles::add_callback(&cb);
struct document_callback_t : document::document_t::callback_t
{
document_callback_t (BundleEditor* self) : _self(self) { }
void handle_document_event (document::document_ptr document, event_t event)
{
switch(event)
{
case did_change_modified_status:
[_self didChangeModifiedState];
break;
}
}
private:
BundleEditor* _self;
};
documentCallback = new document_callback_t(self);
}
return SharedInstance;
}
- (void)dealloc
{
if(bundleItemContent)
bundleItemContent->remove_callback(documentCallback);
delete documentCallback;
[sharedPropertiesViewController release];
[extraPropertiesViewController release];
self.bundleItemProperties = nil;
[drawer release];
[super dealloc];
}
- (void)windowDidLoad
{
static struct { NSString* name; NSArray* array; } const converters[] =
{
{ @"OakSaveStringListTransformer", @[ @"nop", @"saveActiveFile", @"saveModifiedFiles" ] },
{ @"OakInputStringListTransformer", @[ @"selection", @"document", @"scope", @"line", @"word", @"character", @"none" ] },
{ @"OakInputFormatStringListTransformer", @[ @"text", @"xml" ] },
{ @"OakOutputLocationStringListTransformer", @[ @"replaceInput", @"replaceDocument", @"atCaret", @"afterInput", @"newWindow", @"toolTip", @"discard", @"replaceSelection" ] },
{ @"OakOutputFormatStringListTransformer", @[ @"text", @"snippet", @"html", @"completionList" ] },
{ @"OakOutputCaretStringListTransformer", @[ @"afterOutput", @"selectOutput", @"interpolateByChar", @"interpolateByLine", @"heuristic" ] },
};
[OakRot13Transformer register];
for(size_t i = 0; i != sizeofA(converters); ++i)
[OakStringListTransformer createTransformerWithName:converters[i].name andObjectsArray:converters[i].array];
drawer = [[NSDrawer alloc] initWithContentSize:NSZeroSize preferredEdge:NSMaxXEdge];
[drawer setParentWindow:[self window]];
bundles = be::bundle_entries();
[browser setDelegate:self];
[browser loadColumnZero];
if([browser respondsToSelector:@selector(setAutohidesScroller:)])
[browser setAutohidesScroller:YES];
[[self window] makeFirstResponder:browser];
}
- (void)didChangeBundleItems
{
std::vector<std::string> selection;
be::entry_ptr entry = bundles;
for(NSInteger col = 0; col < [browser lastColumn]+1; ++col)
{
NSInteger row = [browser selectedRowInColumn:col];
if(row == -1)
break;
entry = entry->children()[row];
selection.push_back(entry->identifier());
}
bundles = be::bundle_entries();
[browser loadColumnZero];
entry = bundles;
for(size_t col = 0; col < selection.size(); ++col)
{
for(size_t row = 0; row < entry->children().size(); ++row)
{
if(selection[col] == entry->children()[row]->identifier())
{
[browser selectRow:row inColumn:col];
entry = entry->children()[row];
break;
}
}
}
}
- (void)didChangeModifiedState
{
[self setDocumentEdited:bundleItem && (changes.find(bundleItem) != changes.end() || propertiesChanged || bundleItemContent->is_modified())];
}
// ==================
// = Action Methods =
// ==================
- (void)createItemOfType:(bundles::kind_t)aType
{
NSString* path = [[NSBundle bundleForClass:[self class]] pathForResource:info_for(aType).file ofType:@"plist"];
if(!path || ![[NSFileManager defaultManager] fileExistsAtPath:path])
return;
NSInteger row = [browser selectedRowInColumn:0];
bundles::item_ptr bundle = row != -1 ? bundles->children()[row]->represented_item() : bundles::item_ptr();
if(aType == bundles::kItemTypeBundle || bundle)
{
std::map<std::string, std::string> environment = variables_for_path();
ABMutableMultiValue* value = [[[ABAddressBook sharedAddressBook] me] valueForProperty:kABEmailProperty];
if(NSString* email = [value valueAtIndex:[value indexForIdentifier:[value primaryIdentifier]]])
environment.insert(std::make_pair("TM_ROT13_EMAIL", decode::rot13(to_s(email))));
bundles::item_ptr item(new bundles::item_t(oak::uuid_t().generate(), aType == bundles::kItemTypeBundle ? bundles::item_ptr() : bundle, aType));
plist::dictionary_t plist = plist::load(to_s(path));
expand_visitor_t visitor(environment);
visitor(plist);
plist[bundles::kFieldUUID] = to_s(item->uuid());
if(plist.find(bundles::kFieldName) == plist.end())
plist[bundles::kFieldName] = std::string("untitled");
item->set_plist(plist);
changes.insert(std::make_pair(item, plist));
bundles::add_item(item);
[self revealBundleItem:item];
[self didChangeModifiedState];
}
}
- (void)createItemSheetDidEnd:(NSAlert*)alert returnCode:(NSInteger)returnCode contextInfo:(void*)info
{
if(returnCode == NSAlertDefaultReturn)
[self createItemOfType:(bundles::kind_t)[[(NSPopUpButton*)[alert accessoryView] selectedItem] tag]];
[alert release];
}
- (void)newDocument:(id)sender
{
// kItemTypeMacro, kItemTypeMenu, kItemTypeMenuItemSeparator
NSMenu* menu = [[[NSMenu alloc] initWithTitle:@"Item Types"] autorelease];
iterate(it, item_infos)
{
static int const types = bundles::kItemTypeBundle|bundles::kItemTypeCommand|bundles::kItemTypeDragCommand|bundles::kItemTypeSnippet|bundles::kItemTypeSettings|bundles::kItemTypeGrammar|bundles::kItemTypeProxy|bundles::kItemTypeTheme;
if((it->kind & types) == it->kind)
[[menu addItemWithTitle:it->file action:NULL keyEquivalent:@""] setTag:it->kind];
}
NSAlert* alert = [[NSAlert alertWithMessageText:@"Create New Item" defaultButton:@"Create" alternateButton:@"Cancel" otherButton:nil informativeTextWithFormat:@"Please choose what you want to create:"] retain];
NSPopUpButton* typeChooser = [[[NSPopUpButton alloc] initWithFrame:NSZeroRect pullsDown:NO] autorelease];
[typeChooser setMenu:menu];
[typeChooser sizeToFit];
[alert setAccessoryView:typeChooser];
[alert beginSheetModalForWindow:[self window] modalDelegate:self didEndSelector:@selector(createItemSheetDidEnd:returnCode:contextInfo:) contextInfo:NULL];
[[alert window] recalculateKeyViewLoop];
[[alert window] makeFirstResponder:typeChooser];
}
- (void)delete:(id)sender
{
if(bundleItem && bundleItem->move_to_trash())
{
OakPlayUISound(OakSoundDidTrashItemUISound);
bundles::item_ptr trashedItem = bundleItem;
bundles::item_ptr newSelectedItem;
bool foundItem = false;
iterate(entry, parent_for_column(browser, [browser selectedColumn], bundles)->children())
{
if(bundles::item_ptr item = (*entry)->represented_item())
{
if(item->uuid() == bundleItem->uuid())
foundItem = true;
else if(item->kind() != bundles::kItemTypeMenu && item->kind() != bundles::kItemTypeMenuItemSeparator)
newSelectedItem = item;
if(foundItem && newSelectedItem)
break;
}
}
if(newSelectedItem)
[self revealBundleItem:newSelectedItem];
changes.erase(trashedItem);
bundles::remove_item(trashedItem);
[self didChangeModifiedState];
}
}
- (void)revealBundleItem:(bundles::item_ptr const&)anItem
{
[self showWindow:self];
[self setBundleItem:anItem];
std::vector<be::entry_ptr> const& allBundles = bundles->children();
iterate(bundle, allBundles)
{
if((anItem->bundle() ?: anItem) != (*bundle)->represented_item())
continue;
[browser selectRow:(bundle - allBundles.begin()) inColumn:0];
for(std::vector< std::pair<std::vector<be::entry_ptr>, int> > stack(1, std::make_pair((*bundle)->children(), -1)); !stack.empty(); stack.pop_back())
{
for(++stack.back().second; stack.back().second < stack.back().first.size(); ++stack.back().second)
{
be::entry_ptr entry = stack.back().first[stack.back().second];
if(entry->has_children())
{
stack.push_back(std::make_pair(entry->children(), -1));
}
else if(entry->represented_item() == anItem)
{
for(size_t j = 0; j < stack.size(); ++j)
[browser selectRow:stack[j].second inColumn:j+1];
return;
}
}
}
}
}
- (BOOL)commitEditing
{
if(!bundleItem)
return YES;
[sharedPropertiesViewController commitEditing];
[extraPropertiesViewController commitEditing];
if(!propertiesChanged && !bundleItemContent->is_modified())
return YES;
plist::dictionary_t plist = plist::convert((CFPropertyListRef)bundleItemProperties);
std::string const& content = bundleItemContent->buffer().substr(0, bundleItemContent->buffer().size());
item_info_t const& info = info_for(bundleItem->kind());
if(info.plist_key == NULL_STR)
{
plist::any_t any = plist::parse_ascii(content);
if(plist::dictionary_t const* grammarPlist = boost::get<plist::dictionary_t>(&any))
{
static std::string const keys[] = { "comment", "patterns", "repository", "injections" };
iterate(key, keys)
{
if(grammarPlist->find(*key) != grammarPlist->end())
plist[*key] = grammarPlist->find(*key)->second;
else plist.erase(*key);
}
}
}
else
{
if(oak::contains(beginof(PlistItemKinds), endof(PlistItemKinds), info.kind))
plist[info.plist_key] = plist::parse_ascii(content);
else plist[info.plist_key] = content;
}
switch(info.kind)
{
case bundles::kItemTypeGrammar:
plist[bundles::kFieldGrammarExtension] = unwrap_array([bundleItemProperties objectForKey:[NSString stringWithCxxString:bundles::kFieldGrammarExtension]], @"extension");
break;
case bundles::kItemTypeDragCommand:
plist[bundles::kFieldDropExtension] = unwrap_array([bundleItemProperties objectForKey:[NSString stringWithCxxString:bundles::kFieldDropExtension]], @"extension");;
break;
}
if(plist::equal(plist, bundleItem->plist()))
changes.erase(bundleItem);
else changes[bundleItem] = plist;
propertiesChanged = NO;
bundleItemContent->set_disk_revision(bundleItemContent->revision());
[self didChangeModifiedState];
return YES;
}
- (void)saveDocument:(id)sender
{
[self commitEditing];
iterate(pair, changes)
{
pair->first->set_plist(pair->second);
pair->first->save();
}
changes.clear();
[self didChangeModifiedState];
}
// =====================
// = NSBrowserDelegate =
// =====================
- (NSInteger)browser:(NSBrowser*)aBrowser numberOfRowsInColumn:(NSInteger)aColumn
{
be::entry_ptr entry = parent_for_column(aBrowser, aColumn, bundles);
return entry && entry->has_children() ? entry->children().size() : 0;
}
- (void)browser:(NSBrowser*)aBrowser willDisplayCell:(id)aCell atRow:(NSInteger)aRow column:(NSInteger)aColumn
{
if(NSBrowserCell* cell = [aCell isKindOfClass:[NSBrowserCell class]] ? aCell : nil)
{
be::entry_ptr entry = parent_for_column(aBrowser, aColumn, bundles)->children()[aRow];
[cell setStringValue:[NSString stringWithCxxString:entry->name()]];
[cell setLeaf:!entry->has_children()];
[cell setEnabled:!entry->disabled()];
[cell setLoaded:YES];
if(bundles::item_ptr item = entry->represented_item())
{
[cell setImage:[NSImage imageNamed:info_for(item->kind()).file inSameBundleAsClass:[self class]]];
}
else
{
std::string const& path = entry->represented_path();
if(path != NULL_STR)
[cell setImage:[OakFileIconImage fileIconImageWithPath:[NSString stringWithCxxString:path] size:NSMakeSize(16, 16)]];
}
}
}
// ====================
// = NSBrowser Target =
// ====================
- (IBAction)browserSelectionDidChange:(id)sender
{
NSInteger aColumn = [browser selectedColumn];
NSInteger aRow = aColumn != -1 ? [browser selectedRowInColumn:aColumn] : -1;
if(aColumn != -1 && aRow != -1)
{
if(bundles::item_ptr item = parent_for_column(browser, aColumn, bundles)->children()[aRow]->represented_item())
{
if(item->kind() != bundles::kItemTypeMenu && item->kind() != bundles::kItemTypeMenuItemSeparator)
[self setBundleItem:item];
}
}
}
// =======================
// = Setting Bundle Item =
// =======================
- (void)observeValueForKeyPath:(NSString*)aKeyPath ofObject:(id)anObject change:(NSDictionary*)someChange context:(void*)context
{
propertiesChanged = YES;
[self didChangeModifiedState];
}
- (void)setBundleItemProperties:(NSMutableDictionary*)someProperties
{
static std::string const BindingKeys[] = { bundles::kFieldIsDisabled, bundles::kFieldName, bundles::kFieldKeyEquivalent, bundles::kFieldTabTrigger, bundles::kFieldScopeSelector, bundles::kFieldSemanticClass, bundles::kFieldContentMatch, bundles::kFieldHideFromUser, bundles::kFieldDropExtension, bundles::kFieldGrammarExtension, bundles::kFieldGrammarFirstLineMatch, bundles::kFieldGrammarScope, bundles::kFieldGrammarInjectionSelector, "beforeRunningCommand", "input", "inputFormat", "outputLocation", "outputFormat", "outputCaret", "autoScrollOutput", "contactName", "contactEmailRot13", "description", "disableAutoIndent", "useGlobalClipboard", "author", "comment" };
NSMutableDictionary* oldProperties = bundleItemProperties;
bundleItemProperties = [someProperties retain];
iterate(str, BindingKeys)
{
NSString* key = [NSString stringWithCxxString:*str];
[oldProperties removeObserver:self forKeyPath:key];
[someProperties addObserver:self forKeyPath:key options:0 context:NULL];
}
[oldProperties release];
propertiesChanged = NO;
}
static NSMutableDictionary* DictionaryForBundleItem (bundles::item_ptr const& aBundleItem)
{
NSMutableDictionary* res = ns::to_mutable_dictionary(aBundleItem->plist());
switch(info_for(aBundleItem->kind()).kind)
{
case bundles::kItemTypeCommand:
{
bundle_command_t cmd = parse_command(aBundleItem);
struct { NSString* key; int index; NSArray* array; } const popups[] =
{
{ @"beforeRunningCommand", cmd.pre_exec, @[ @"nop", @"saveActiveFile", @"saveModifiedFiles" ] },
{ @"input", cmd.input, @[ @"selection", @"document", @"scope", @"line", @"word", @"character", @"none" ] },
{ @"inputFormat", cmd.input_format, @[ @"text", @"xml" ] },
{ @"outputLocation", cmd.output, @[ @"replaceInput", @"replaceDocument", @"atCaret", @"afterInput", @"newWindow", @"toolTip", @"discard", @"replaceSelection" ] },
{ @"outputFormat", cmd.output_format, @[ @"text", @"snippet", @"html", @"completionList" ] },
{ @"outputCaret", cmd.output_caret, @[ @"afterOutput", @"selectOutput", @"interpolateByChar", @"interpolateByLine", @"heuristic" ] },
};
[res removeObjectForKey:@"output"];
[res removeObjectForKey:@"dontFollowNewOutput"];
[res setObject:@2 forKey:@"version"];
if(cmd.auto_scroll_output)
[res setObject:YES_obj forKey:@"autoScrollOutput"];
for(size_t i = 0; i != sizeofA(popups); ++i)
[res setObject:[popups[i].array objectAtIndex:popups[i].index] forKey:popups[i].key];
}
break;
case bundles::kItemTypeGrammar:
{
[res setObject:wrap_array(aBundleItem->values_for_field(bundles::kFieldGrammarExtension), @"extension") forKey:[NSString stringWithCxxString:bundles::kFieldGrammarExtension]];
}
break;
case bundles::kItemTypeDragCommand:
{
[res setObject:wrap_array(aBundleItem->values_for_field(bundles::kFieldDropExtension), @"extension") forKey:[NSString stringWithCxxString:bundles::kFieldDropExtension]];
}
break;
}
return res;
}
static NSMutableDictionary* DictionaryForPropertyList (plist::dictionary_t const& plist, bundles::item_ptr const& aBundleItem)
{
NSMutableDictionary* res = ns::to_mutable_dictionary(plist);
switch(info_for(aBundleItem->kind()).kind)
{
case bundles::kItemTypeGrammar:
[res setObject:wrap_array(aBundleItem->values_for_field(bundles::kFieldGrammarExtension), @"extension") forKey:[NSString stringWithCxxString:bundles::kFieldGrammarExtension]];
break;
case bundles::kItemTypeDragCommand:
[res setObject:wrap_array(aBundleItem->values_for_field(bundles::kFieldDropExtension), @"extension") forKey:[NSString stringWithCxxString:bundles::kFieldDropExtension]];
break;
}
return res;
}
- (bundles::item_ptr const&)bundleItem
{
return bundleItem;
}
- (BOOL)window:(NSWindow*)aWindow shouldDragDocumentWithEvent:(NSEvent*)anEvent from:(NSPoint)dragImageLocation withPasteboard:(NSPasteboard*)aPasteboard { return NO; }
- (BOOL)window:(NSWindow*)aWindow shouldPopUpDocumentPathMenu:(NSMenu*)aMenu { return NO; }
- (void)setBundleItem:(bundles::item_ptr const&)aBundleItem
{
if(bundleItem == aBundleItem)
return;
[self commitEditing];
if(bundleItemContent)
bundleItemContent->remove_callback(documentCallback);
bundleItem = aBundleItem;
bundleItemContent = document::document_ptr();
std::map<bundles::item_ptr, plist::dictionary_t>::const_iterator it = changes.find(bundleItem);
self.bundleItemProperties = it != changes.end() ? DictionaryForPropertyList(it->second, bundleItem) : DictionaryForBundleItem(bundleItem);
item_info_t const& info = info_for(bundleItem->kind());
[[self window] setTitle:[NSString stringWithCxxString:bundleItem->full_name()]];
[[self window] setRepresentedFilename:NSHomeDirectory()];
[[[self window] standardWindowButton:NSWindowDocumentIconButton] setImage:[[NSWorkspace sharedWorkspace] iconForFileType:[NSString stringWithCxxString:info.file_type]]];
plist::dictionary_t const& plist = it != changes.end() ? it->second : bundleItem->plist();
if(info.plist_key == NULL_STR)
{
plist::dictionary_t grammarPlist;
static std::string const keys[] = { "comment", "patterns", "repository", "injections" };
iterate(key, keys)
{
if(plist.find(*key) != plist.end())
grammarPlist[*key] = plist.find(*key)->second;
}
bundleItemContent = document::from_content(to_s(grammarPlist, plist::kPreferSingleQuotedStrings), info.grammar);
}
else if(oak::contains(beginof(PlistItemKinds), endof(PlistItemKinds), info.kind))
{
if(plist.find(info.plist_key) != plist.end())
bundleItemContent = document::from_content(to_s(plist.find(info.plist_key)->second, plist::kPreferSingleQuotedStrings), info.grammar);
}
else
{
std::string str;
if(plist::get_key_path(plist, info.plist_key, str))
{
if(info.kind == bundles::kItemTypeCommand || info.kind == bundles::kItemTypeDragCommand)
command::fix_shebang(&str);
bundleItemContent = document::from_content(str, info.grammar);
}
}
bundleItemContent = bundleItemContent ?: document::from_content("");
bundleItemContent->add_callback(documentCallback);
[documentView setDocument:bundleItemContent];
self.sharedPropertiesViewController = [[[PropertiesViewController alloc] initWithName:@"SharedProperties"] autorelease];
self.extraPropertiesViewController = nil;
[sharedPropertiesViewController setProperties:bundleItemProperties];
if(info.kind == bundles::kItemTypeBundle)
self.sharedPropertiesViewController = nil;
NSView* sharedView = [sharedPropertiesViewController view];
if(info.view_controller)
{
self.extraPropertiesViewController = [[[PropertiesViewController alloc] initWithName:info.view_controller] autorelease];
[extraPropertiesViewController setProperties:bundleItemProperties];
NSView* extraView = [extraPropertiesViewController view];
CGFloat delta = extraPropertiesViewController.indent - sharedPropertiesViewController.indent;
if(delta > 0)
[sharedView setFrame:NSOffsetRect(sharedView.frame, delta, 0)];
else [extraView setFrame:NSOffsetRect(extraView.frame, -delta, 0)];
NSView* contentView = [[[NSView alloc] initWithFrame:NSMakeRect(0, 0, std::max(NSMaxX(sharedView.frame) + 1, NSMaxX(extraView.frame) + 1), NSHeight(sharedView.frame) + NSHeight(extraView.frame))] autorelease];
[sharedView setAutoresizingMask:NSViewMinXMargin|NSViewMinYMargin];
[extraView setAutoresizingMask:NSViewMinXMargin|NSViewMinYMargin];
[sharedView setFrame:NSOffsetRect(sharedView.frame, 0, NSHeight(extraView.frame))];
[contentView addSubview:sharedView];
[contentView addSubview:extraView];
[drawer setContentView:[[[NSView alloc] initWithFrame:NSZeroRect] autorelease]];
[drawer setContentSize:contentView.frame.size];
[drawer setContentView:contentView];
[drawer open:self];
}
else
{
[sharedView setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
[drawer setContentSize:sharedView.frame.size];
[drawer setContentView:sharedView];
[drawer open:self];
}
[[[drawer contentView] window] recalculateKeyViewLoop];
}
- (void)closeWarningDidEnd:(NSAlert*)alert returnCode:(NSInteger)returnCode contextInfo:(void*)info
{
if(returnCode != NSAlertSecondButtonReturn) // Not "Cancel"
{
if(returnCode == NSAlertFirstButtonReturn) // "Save"
[self saveDocument:self];
else if(returnCode == NSAlertThirdButtonReturn) // "Dont Save"
changes.clear();
[self close];
}
[alert release];
}
static NSString* DescriptionForChanges (std::map<bundles::item_ptr, plist::dictionary_t> const& changes)
{
NSString* res = [NSString stringWithCxxString:text::format("Do you want to save the changes made to %zu items?", changes.size())];
if(changes.size() == 1)
{
bundles::item_ptr item = changes.begin()->first;
NSString* name = [NSString stringWithCxxString:item->name()];
if(item->kind() == bundles::kItemTypeBundle)
{
res = [NSString stringWithFormat:@"Do you want to save the changes made to the bundle named “%@”?", name];
}
else
{
NSString* bundleName = [NSString stringWithCxxString:item->bundle()->name()];
NSString* type = [info_for(item->kind()).file lowercaseString];
res = [NSString stringWithFormat:@"Do you want to save the changes made to the %@ item named “%@” in the “%@” bundle?", type, name, bundleName];
}
}
return res;
}
- (BOOL)windowShouldClose:(id)sender
{
[self commitEditing];
if(changes.empty())
return YES;
NSAlert* alert = [[NSAlert alloc] init];
[alert setAlertStyle:NSWarningAlertStyle];
[alert setMessageText:DescriptionForChanges(changes)];
[alert setInformativeText:@"Your changes will be lost if you don't save them."];
[alert addButtons:@"Save", @"Cancel", @"Dont Save", nil];
[alert beginSheetModalForWindow:self.window modalDelegate:self didEndSelector:@selector(closeWarningDidEnd:returnCode:contextInfo:) contextInfo:NULL];
return NO;
}
@end