Initial commit

This commit is contained in:
Allan Odgaard
2012-08-09 16:25:56 +02:00
commit 9894969e67
1103 changed files with 215580 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
#ifndef DOCUMENT_COMMAND_H_KLPQDYHU
#define DOCUMENT_COMMAND_H_KLPQDYHU
#import <document/document.h>
#import <document/collection.h>
#import <command/parser.h>
void run (bundle_command_t const& command, ng::buffer_t const& buffer, ng::ranges_t const& selection, document::document_ptr document, std::map<std::string, std::string> env, document::run_callback_ptr callback);
void show_command_error (std::string const& message, oak::uuid_t const& uuid, NSWindow* window = nil);
#endif /* end of include guard: DOCUMENT_COMMAND_H_KLPQDYHU */

View File

@@ -0,0 +1,334 @@
#import "DocumentCommand.h"
#import "DocumentController.h"
#import "DocumentTabs.h"
#import "DocumentSaveHelper.h"
#import <OakAppKit/OakToolTip.h>
#import <OakAppKit/NSAlert Additions.h>
#import <OakFoundation/NSString Additions.h>
#import <BundleEditor/BundleEditor.h>
#import <HTMLOutputWindow/HTMLOutputWindow.h>
#import <OakTextView/OakDocumentView.h>
#import <OakSystem/application.h>
#import <OakSystem/process.h>
#import <command/runner.h>
#import <ns/ns.h>
#import <oak/oak.h>
#import <bundles/bundles.h>
#import <document/collection.h>
#import <editor/editor.h>
#import <editor/write.h>
#import <io/path.h>
#import <text/trim.h>
#import <text/tokenize.h>
@interface OakShowCommandErrorDelegate : NSObject
{
oak::uuid_t commandUUID;
}
@end
@implementation OakShowCommandErrorDelegate
- (id)initWithCommandUUID:(oak::uuid_t const&)anUUID
{
if(self = [super init])
commandUUID = anUUID;
return self;
}
- (void)commandErrorDidEnd:(NSAlert*)alert returnCode:(NSInteger)returnCode contextInfo:(void*)info
{
if(returnCode == NSAlertSecondButtonReturn)
[[BundleEditor sharedInstance] revealBundleItem:bundles::lookup(commandUUID)];
[alert release];
[self release];
}
@end
@interface DocumentController (Variables)
- (void)updateVariables:(std::map<std::string, std::string>&)env;
@end
@implementation DocumentController (Variables)
- (void)updateVariables:(std::map<std::string, std::string>&)env
{
[fileBrowser updateVariables:env];
if(NSString* projectDir = self.projectPath)
{
env["TM_PROJECT_DIRECTORY"] = [projectDir fileSystemRepresentation];
env["TM_PROJECT_UUID"] = to_s(identifier);
}
}
@end
namespace
{
struct delegate_t : command::delegate_t
{
delegate_t (DocumentController* controller, document::document_ptr document) : _controller(controller), _document(document), _did_open_html_window(false)
{
if(_controller)
_collection = to_s(_controller.identifier);
}
text::range_t write_unit_to_fd (int fd, input::type unit, input::type fallbackUnit, input_format::type format, scope::selector_t const& scopeSelector, std::map<std::string, std::string>& variables, bool* inputWasSelection);
bool accept_html_data (command::runner_ptr runner, char const* data, size_t len);
bool accept_result (std::string const& out, output::type placement, output_format::type format, output_caret::type outputCaret, text::range_t inputRange, std::map<std::string, std::string> const& environment);
void show_tool_tip (std::string const& str);
void show_document (std::string const& str);
void show_error (bundle_command_t const& command, int rc, std::string const& out, std::string const& err);
private:
DocumentController* _controller;
document::document_ptr _document;
oak::uuid_t _collection;
bool _did_open_html_window;
};
}
// =========================
// = Checking requirements =
// =========================
static std::string find_first_executable (std::vector<std::string> const& locations, std::map<std::string, std::string> const& environment)
{
iterate(path, locations)
{
std::string const exe = format_string::expand(*path, environment);
if(path::is_executable(exe))
return exe;
}
return NULL_STR;
}
static std::vector<std::string> search_paths (std::map<std::string, std::string> const& environment)
{
std::vector<std::string> res;
auto searchPath = environment.find("PATH");
if(searchPath != environment.end())
{
citerate(it, text::tokenize(searchPath->second.begin(), searchPath->second.end(), ':'))
{
if(*it != "")
res.push_back(*it);
}
}
else
{
fprintf(stderr, "no PATH!!!\n");
iterate(pair, environment)
fprintf(stderr, "%s = %s\n", pair->first.c_str(), pair->second.c_str());
}
return res;
}
static bool find_executable (std::string const& command, std::string const& variable, std::map<std::string, std::string> const& environment)
{
auto var = environment.find(variable);
if(var != environment.end())
return path::is_executable(var->second);
citerate(it, search_paths(environment))
{
if(path::is_executable(path::join(*it, command)))
return true;
}
return false;
}
// =======================
// = Init, Saving, Input =
// =======================
text::range_t delegate_t::write_unit_to_fd (int fd, input::type unit, input::type fallbackUnit, input_format::type format, scope::selector_t const& scopeSelector, std::map<std::string, std::string>& variables, bool* inputWasSelection)
{
if(!_document)
return text::range_t::undefined;
bool isOpen = _document->is_open();
if(!isOpen)
_document->open();
text::range_t res = ng::write_unit_to_fd(_document->buffer(), ng::editor_for_document(_document)->ranges().last(), _document->buffer().indent().tab_size(), fd, unit, fallbackUnit, format, scopeSelector, variables, inputWasSelection);
if(!isOpen)
_document->close();
return res;
}
// ====================
// = Accepting Output =
// ====================
bool delegate_t::accept_html_data (command::runner_ptr runner, char const* data, size_t len)
{
if(!_did_open_html_window)
{
_did_open_html_window = true;
if(_controller)
{
if(![_controller setCommandRunner:runner])
oak::kill_process_group_in_background(runner->process_id());
}
else
{
[HTMLOutputWindowController HTMLOutputWindowWithRunner:runner];
}
}
return true;
}
bool delegate_t::accept_result (std::string const& out, output::type placement, output_format::type format, output_caret::type outputCaret, text::range_t inputRange, std::map<std::string, std::string> const& environment)
{
bool res;
if(_document && _document->is_open())
{
res = ng::editor_for_document(_document)->handle_result(out, placement, format, outputCaret, inputRange, environment);
}
else
{
document::document_ptr doc = document::create();
doc->open();
res = ng::editor_for_document(doc)->handle_result(out, placement, format, outputCaret, text::pos_t(0, 0) /* inputRange */, environment);
document::show(doc);
doc->close();
}
return res;
}
// ========================================
// = Showing tool tip, document, or error =
// ========================================
void delegate_t::show_tool_tip (std::string const& str)
{
NSPoint location = _controller ? [_controller positionForWindowUnderCaret] : [NSEvent mouseLocation];
OakShowToolTip([NSString stringWithCxxString:str], location);
}
void delegate_t::show_document (std::string const& str)
{
document::show(document::from_content(str), _collection);
}
void delegate_t::show_error (bundle_command_t const& command, int rc, std::string const& out, std::string const& err)
{
show_command_error(text::trim(err + out).empty() ? text::format("Command returned status code %d.", rc) : err + out, command.uuid, _controller.window);
}
// ==============
// = Public API =
// ==============
void run (bundle_command_t const& command, ng::buffer_t const& buffer, ng::ranges_t const& selection, document::document_ptr document, std::map<std::string, std::string> baseEnv, document::run_callback_ptr callback)
{
DocumentController* controller = [DocumentController controllerForDocument:document];
std::vector<document::document_ptr> documentsToSave;
switch(command.pre_exec)
{
case pre_exec::save_document:
{
if(document && (document->is_modified() || !document->is_on_disk()))
documentsToSave.push_back(document);
}
break;
case pre_exec::save_project:
{
if(controller)
{
iterate(tab, controller->documentTabs)
{
document::document_ptr doc = **tab;
if(doc->is_modified() && doc->path() != NULL_STR)
documentsToSave.push_back(doc);
}
}
}
break;
}
if(!documentsToSave.empty())
{
struct callback_t : document_save_callback_t
{
callback_t (bundle_command_t const& command, ng::buffer_t const& buffer, ng::ranges_t const& selection, document::document_ptr document, std::map<std::string, std::string> const& environment, document::run_callback_ptr callback, size_t count) : _command(command), _buffer(buffer), _selection(selection), _document(document), _environment(environment), _callback(callback), _count(count) { }
void did_save_document (document::document_ptr document, bool flag, std::string const& message, oak::uuid_t const& filter)
{
if(--_count == 0 && flag)
::run(_command, _buffer, _selection, _document, _environment, _callback);
if(_count == 0 || !flag)
delete this;
}
private:
bundle_command_t _command;
ng::buffer_t const& _buffer;
ng::ranges_t const& _selection;
document::document_ptr _document;
std::map<std::string, std::string> _environment;
document::run_callback_ptr _callback;
size_t _count;
};
[DocumentSaveHelper trySaveDocuments:documentsToSave forWindow:controller.window defaultDirectory:controller.untitledSavePath andCallback:new callback_t(command, buffer, selection, document, baseEnv, callback, documentsToSave.size())];
}
else
{
if(document && document->is_open())
baseEnv = ng::editor_for_document(document)->variables(baseEnv);
else if(document)
baseEnv = document->variables(baseEnv);
else
baseEnv = variables_for_path(NULL_STR, "", baseEnv);
if(controller)
[controller updateVariables:baseEnv];
if(callback)
callback->update_environment(baseEnv);
bundles::item_ptr item = bundles::lookup(command.uuid);
if(item)
baseEnv = item->environment(baseEnv);
iterate(it, command.requirements)
{
if(find_executable(it->command, it->variable, baseEnv))
continue;
std::string exe = find_first_executable(it->locations, baseEnv);
if(exe == NULL_STR)
return show_command_error(text::format("This command requires %1$s which wasnt found on your system.\n\nThe following locations were searched: %2$s.\n\nIf %1$s is installed elsewhere then you need to set %3$s in Preferences → Variables to the full path of where you installed it.", it->command.c_str(), text::join(search_paths(baseEnv), ", ").c_str(), it->variable.c_str()), command.uuid, [controller window]);
if(it->variable != NULL_STR)
baseEnv[it->variable] = exe;
else baseEnv["PATH"] += ":" + path::parent(exe);
}
command::runner_ptr runner = command::runner(command, buffer, selection, baseEnv, command::delegate_ptr((command::delegate_t*)new delegate_t(controller, document)));
runner->launch();
runner->wait();
}
}
void show_command_error (std::string const& message, oak::uuid_t const& uuid, NSWindow* window)
{
std::string commandName = "(unknown)";
if(bundles::item_ptr item = bundles::lookup(uuid))
commandName = item->name();
NSAlert* alert = [[NSAlert alloc] init]; // released in didEndSelector
[alert setAlertStyle:NSCriticalAlertStyle];
[alert setMessageText:[NSString stringWithCxxString:text::format("Failure running “%.*s”.", (int)commandName.size(), commandName.data())]];
[alert setInformativeText:[NSString stringWithCxxString:message] ?: @"No output"];
[alert addButtons:@"OK", @"Edit Command", nil];
OakShowCommandErrorDelegate* delegate = [[OakShowCommandErrorDelegate alloc] initWithCommandUUID:uuid];
if(window)
[alert beginSheetModalForWindow:window modalDelegate:delegate didEndSelector:@selector(commandErrorDidEnd:returnCode:contextInfo:) contextInfo:NULL];
else [delegate commandErrorDidEnd:alert returnCode:[alert runModal] contextInfo:NULL];
}

View File

@@ -0,0 +1,111 @@
#import "DocumentOpenHelper.h"
#import <OakFileBrowser/OakFileBrowser.h>
#import <OakAppKit/OakTabBarView.h>
#import <oak/debug.h>
#import <plist/uuid.h>
#import <command/runner.h>
#import <scm/scm.h>
extern NSString* const kUserDefaultsHTMLOutputPlacementKey;
extern NSString* const kUserDefaultsFileBrowserPlacementKey;
@class OakLayoutView;
@class OakDocumentView;
@class OakTextView;
@class OakFilterWindowController;
@class OakHTMLOutputView;
struct document_tab_t;
typedef std::tr1::shared_ptr<document_tab_t> document_tab_ptr;
namespace bundles { struct item_t; typedef std::tr1::shared_ptr<item_t> item_ptr; }
@interface DocumentController : NSWindowController <OakFileBrowserDelegate, OakTabBarViewDelegate, OakTabBarViewDataSource, DocumentOpenHelperDelegate>
{
OBJC_WATCH_LEAKS(DocumentController);
IBOutlet OakTabBarView* tabBarView;
IBOutlet OakLayoutView* layoutView;
OakFileBrowser* fileBrowser;
OakDocumentView* documentView;
OakTextView* textView;
OakHTMLOutputView* htmlOutputView;
command::runner_ptr runner;
BOOL windowHasLoaded;
BOOL fileBrowserHidden;
NSDictionary* fileBrowserState;
int32_t fileBrowserWidth;
int32_t htmlOutputHeight;
OakFilterWindowController* filterWindowController;
NSUInteger fileChooserSourceIndex;
// =====================
// = Document Bindings =
// =====================
NSString* windowTitle;
NSString* representedFile;
BOOL isDocumentEdited;
scm::info_ptr scmInfo;
scm::callback_t* scmCallback;
// =================
// = Document Tabs =
// =================
oak::uuid_t identifier;
@public // FIXME
std::vector<document_tab_ptr> documentTabs;
@protected
size_t selectedTabIndex;
oak::uuid_t scratchDocument;
}
@property (nonatomic, readonly) NSString* identifier;
@property (nonatomic, assign) BOOL fileBrowserHidden;
@property (nonatomic, readonly) NSString* documentPath;
@property (nonatomic, readonly) NSString* fileBrowserPath;
@property (nonatomic, readonly) NSString* projectPath;
@property (nonatomic, readonly) NSString* untitledSavePath;
+ (DocumentController*)controllerForDocument:(document::document_ptr const&)aDocument;
+ (DocumentController*)controllerForUUID:(oak::uuid_t const&)aUUID;
- (id)init;
- (IBAction)goToFileCounterpart:(id)sender;
- (IBAction)selectNextTab:(id)sender;
- (IBAction)selectPreviousTab:(id)sender;
- (IBAction)takeSelectedTabIndexFrom:(id)sender;
- (IBAction)revealFileInProject:(id)sender;
- (IBAction)toggleFileBrowser:(id)sender;
- (void)performBundleItem:(bundles::item_ptr const&)anItem;
- (NSPoint)positionForWindowUnderCaret;
- (void)performCloseWindow:(id)sender;
- (void)performCloseOtherTabs:(id)sender;
- (void)makeTextViewFirstResponder:(id)sender;
- (void)closeTabAtIndex:(NSUInteger)tabIndex;
- (void)closeDocumentWithPath:(NSString*)aPath;
- (BOOL)setCommandRunner:(command::runner_ptr const&)aRunner;
- (IBAction)saveDocument:(id)sender;
- (IBAction)saveDocumentAs:(id)sender;
- (IBAction)saveAllDocuments:(id)sender;
@end
@interface DocumentController (ApplicationTermination)
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication*)sender;
@end

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,18 @@
#import "FileTypeDialog.h"
#import <document/document.h>
@protocol DocumentOpenHelperDelegate;
@interface DocumentOpenHelper : NSObject <FileTypeDialogDelegate>
{
id <DocumentOpenHelperDelegate> delegate;
}
@property (nonatomic, assign) id <DocumentOpenHelperDelegate> delegate;
- (void)tryOpenDocument:(document::document_ptr const&)aDocument forWindow:(NSWindow*)aWindow delegate:(id <DocumentOpenHelperDelegate>)aDelegate;
@end
@protocol DocumentOpenHelperDelegate <NSObject>
@optional
- (void)documentOpenHelper:(DocumentOpenHelper*)documentOpenHelper didOpenDocument:(document::document_ptr const&)aDocument;
- (void)documentOpenHelper:(DocumentOpenHelper*)documentOpenHelper failedToOpenDocument:(document::document_ptr const&)aDocument error:(std::string const&)aMessage usingFilter:(oak::uuid_t const&)filterUUID;
@end

View File

@@ -0,0 +1,136 @@
#import "DocumentOpenHelper.h"
#import "EncodingView.h"
#import <OakFoundation/NSString Additions.h>
#import <ns/ns.h>
#import <text/parse.h>
#import <oak/debug.h>
OAK_DEBUG_VAR(DocumentController_OpenHelper);
@interface DocumentOpenHelper ()
- (void)didOpenDocument:(document::document_ptr const&)aDocument;
- (void)failedToOpenDocument:(document::document_ptr const&)aDocument error:(std::string const&)aMessage usingFilter:(oak::uuid_t const&)filterUUID;
@end
namespace
{
struct info_t
{
info_t (EncodingViewController* controller, file::open_context_ptr context) : controller(controller), context(context) { }
EncodingViewController* controller;
file::open_context_ptr context;
};
struct open_callback_t : document::open_callback_t
{
open_callback_t (DocumentOpenHelper* self, NSWindow* window) : _self(self), _window(window)
{
ASSERT(_window);
}
void select_encoding (std::string const& path, io::bytes_ptr content, file::open_context_ptr context)
{
[_window.attachedSheet orderOut:_self];
NSAlert* alert = [[NSAlert alertWithMessageText:@"Unknown Encoding" defaultButton:@"Continue" alternateButton:@"Cancel" otherButton:nil informativeTextWithFormat:@"This file is not UTF-8 nor does it have any encoding information stored."] retain];
EncodingViewController* controller = [[EncodingViewController alloc] initWithFirst:content->begin() last:content->end()];
[alert setAccessoryView:controller.view];
[alert beginSheetModalForWindow:_window modalDelegate:_self didEndSelector:@selector(selectEncodingSheetDidEnd:returnCode:contextInfo:) contextInfo:new info_t(controller, context)];
[[alert window] recalculateKeyViewLoop];
}
void select_file_type (std::string const& path, io::bytes_ptr content, file::open_context_ptr context)
{
if(path == NULL_STR)
{
context->set_file_type("text.plain");
}
else
{
[_window.attachedSheet orderOut:_self];
FileTypeDialog* controller = [[FileTypeDialog alloc] initWithPath:[NSString stringWithCxxString:path] first:(content ? content->begin() : NULL) last:(content ? content->end() : NULL)];
[controller beginSheetModalForWindow:_window modalDelegate:_self contextInfo:new info_t(nil, context)];
}
}
void show_document (std::string const& path, document::document_ptr document)
{
[_self didOpenDocument:document];
document->close();
}
void show_error (std::string const& path, document::document_ptr document, std::string const& message, oak::uuid_t const& filter)
{
[_self failedToOpenDocument:document error:message usingFilter:filter];
}
private:
DocumentOpenHelper* _self;
NSWindow* _window;
};
}
@implementation DocumentOpenHelper
@synthesize delegate;
- (id)init
{
if(self = [super init])
{
}
return self;
}
- (void)setDelegate:(id <DocumentOpenHelperDelegate>)aDelegate { delegate = aDelegate; }
- (id <DocumentOpenHelperDelegate>)delegate { return delegate; }
- (void)tryOpenDocument:(document::document_ptr const&)aDocument forWindow:(NSWindow*)aWindow delegate:(id <DocumentOpenHelperDelegate>)aDelegate
{
D(DBF_DocumentController_OpenHelper, bug("%s, already open %s\n", aDocument->display_name().c_str(), BSTR(aDocument->is_open())););
[self retain]; // keep us retained until document is opened
delegate = aDelegate;
if(aDocument->try_open(document::open_callback_ptr((document::open_callback_t*)new open_callback_t(self, aWindow))))
{
[self didOpenDocument:aDocument];
aDocument->close();
}
}
- (void)selectEncodingSheetDidEnd:(NSAlert*)alert returnCode:(NSInteger)returnCode contextInfo:(info_t*)info
{
if(returnCode == NSAlertDefaultReturn)
info->context->set_encoding(text::split(to_s(info->controller.currentEncoding), " ")[0]);
[alert release];
[info->controller release];
delete info;
}
- (void)fileTypeDialog:(FileTypeDialog*)fileTypeDialog didSelectFileType:(NSString*)aFileType contextInfo:(void*)info
{
if(aFileType)
((info_t*)info)->context->set_file_type(to_s(aFileType));
[fileTypeDialog release];
delete (info_t*)info;
}
- (void)didOpenDocument:(document::document_ptr const&)aDocument
{
D(DBF_DocumentController_OpenHelper, bug("%s\n", aDocument->display_name().c_str()););
if(aDocument->recent_tracking() && aDocument->path() != NULL_STR)
[[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL:[NSURL fileURLWithPath:[NSString stringWithCxxString:aDocument->path()]]];
if([delegate respondsToSelector:@selector(documentOpenHelper:didOpenDocument:)])
[delegate documentOpenHelper:self didOpenDocument:aDocument];
[self release];
}
- (void)failedToOpenDocument:(document::document_ptr const&)aDocument error:(std::string const&)aMessage usingFilter:(oak::uuid_t const&)filterUUID
{
D(DBF_DocumentController_OpenHelper, bug("%s\n", aDocument->display_name().c_str()););
if([delegate respondsToSelector:@selector(documentOpenHelper:failedToOpenDocument:error:usingFilter:)])
[delegate documentOpenHelper:self failedToOpenDocument:aDocument error:aMessage usingFilter:filterUUID];
[self release];
}
@end

View File

@@ -0,0 +1,23 @@
#import <document/document.h>
#import <file/save.h>
struct PUBLIC document_save_callback_t
{
virtual ~document_save_callback_t () { }
virtual void did_save_document (document::document_ptr document, bool flag, std::string const& message, oak::uuid_t const& filter) = 0;
};
@interface DocumentSaveHelper : NSObject
{
std::vector<document::document_ptr> documents;
NSWindow* window;
NSString* saveFolder;
document_save_callback_t* callback;
file::save_context_ptr context;
BOOL userAbort;
}
+ (void)trySaveDocuments:(std::vector<document::document_ptr> const&)someDocuments forWindow:(NSWindow*)aWindow defaultDirectory:(NSString*)aFolder andCallback:(document_save_callback_t*)aCallback;
+ (void)trySaveDocument:(document::document_ptr const&)aDocument forWindow:(NSWindow*)aWindow defaultDirectory:(NSString*)aFolder andCallback:(document_save_callback_t*)aCallback;
@end
NSString* DefaultSaveNameForDocument (document::document_ptr const& aDocument);

View File

@@ -0,0 +1,246 @@
#import "DocumentSaveHelper.h"
#import "DocumentCommand.h"
#import <OakFoundation/NSString Additions.h>
#import <OakAppKit/OakSavePanel.h>
#import <OakAppKit/OakEncodingPopUpButton.h>
#import <ns/ns.h>
#import <text/parse.h>
#import <regexp/glob.h>
#import <authorization/constants.h>
#import <document/collection.h>
#import <file/encoding.h>
#import <bundles/bundles.h>
OAK_DEBUG_VAR(DocumentController_SaveHelper);
NSString* DefaultSaveNameForDocument (document::document_ptr const& aDocument)
{
citerate(item, bundles::query(bundles::kFieldGrammarScope, aDocument->file_type()))
{
std::string const& ext = (*item)->value_for_field(bundles::kFieldGrammarExtension);
if(ext != NULL_STR)
return [NSString stringWithCxxString:aDocument->display_name() + "." + ext];
}
return [NSString stringWithCxxString:aDocument->display_name()];
}
@interface DocumentSaveHelper ()
- (void)trySaveDocuments:(std::vector<document::document_ptr> const&)someDocuments forWindow:(NSWindow*)aWindow defaultDirectory:(NSString*)aFolder andCallback:(document_save_callback_t*)aCallback;
- (void)didSaveDocument:(document::document_ptr const&)aDocument success:(BOOL)flag error:(std::string const&)aMessage usingFilter:(oak::uuid_t const&)aFilter;
- (file::save_context_ptr const&)context;
- (void)setContext:(file::save_context_ptr const&)newContext;
@property (nonatomic, retain) NSString* saveFolder;
@property (nonatomic, assign) BOOL userAbort;
@end
namespace
{
struct save_callback_t : document::save_callback_t
{
save_callback_t (document::document_ptr document, DocumentSaveHelper* self, NSWindow* window) : _document(document), _self(self), _window(window) { }
void select_path (std::string const& path, io::bytes_ptr content, file::save_context_ptr context)
{
D(DBF_DocumentController_SaveHelper, bug("\n"););
init(context);
[OakSavePanel showWithPath:DefaultSaveNameForDocument(_document) directory:_self.saveFolder fowWindow:_window delegate:_self contextInfo:NULL];
}
void select_make_writable (std::string const& path, io::bytes_ptr content, file::save_context_ptr context)
{
D(DBF_DocumentController_SaveHelper, bug("\n"););
init(context);
// TODO “unlock file” checkbox (presently implied)
NSAlert* alert = [[NSAlert alertWithMessageText:[NSString stringWithCxxString:text::format("The file “%s” is locked.", _document->display_name().c_str())] defaultButton:@"Overwrite" alternateButton:@"Cancel" otherButton:nil informativeTextWithFormat:@"Do you want to overwrite it anyway?"] retain];
[alert beginSheetModalForWindow:_window modalDelegate:_self didEndSelector:@selector(makeWritableSheetDidEnd:returnCode:contextInfo:) contextInfo:NULL];
}
void obtain_authorization (std::string const& path, io::bytes_ptr content, osx::authorization_t auth, file::save_context_ptr context)
{
D(DBF_DocumentController_SaveHelper, bug("\n"););
if(auth.obtain_right(kAuthRightName))
context->set_authorization(auth);
else _self.userAbort = YES;
}
void select_encoding (std::string const& path, io::bytes_ptr content, std::string const& encoding, file::save_context_ptr context)
{
D(DBF_DocumentController_SaveHelper, bug("\n"););
init(context);
if(encoding != kCharsetNoEncoding)
{
// TODO transliteration / BOM check box
NSAlert* alert = [[NSAlert alertWithMessageText:[NSString stringWithCxxString:text::format("Unable to save document using “%s” as encoding.", encoding.c_str())] defaultButton:@"Retry" alternateButton:@"Cancel" otherButton:nil informativeTextWithFormat:@"Please choose another encoding:"] retain];
OakEncodingPopUpButton* encodingChooser = [[[OakEncodingPopUpButton alloc] initWithFrame:NSZeroRect pullsDown:NO] autorelease];
[encodingChooser sizeToFit];
NSRect frame = [encodingChooser frame];
if(NSWidth(frame) > 200)
[encodingChooser setFrameSize:NSMakeSize(200, NSHeight(frame))];
[alert setAccessoryView:encodingChooser];
[alert beginSheetModalForWindow:_window modalDelegate:_self didEndSelector:@selector(encodingSheetDidEnd:returnCode:contextInfo:) contextInfo:NULL];
[[alert window] recalculateKeyViewLoop];
}
else
{
context->set_encoding(kCharsetUTF8);
}
}
void did_save_document (document::document_ptr document, std::string const& path, bool success, std::string const& message, oak::uuid_t const& filter)
{
D(DBF_DocumentController_SaveHelper, bug("%s, %s\n", path.c_str(), BSTR(success)););
[_self didSaveDocument:document success:success error:message usingFilter:filter];
}
private:
void init (file::save_context_ptr context)
{
document::show(_document);
for(NSWindow* window in [NSApp orderedWindows])
{
id delegate = [window delegate];
if(![window isMiniaturized] && [delegate isKindOfClass:[[_window delegate] class]])
{
_window = [delegate window];
break;
}
}
[_window.attachedSheet orderOut:_self];
[_self setContext:context];
}
document::document_ptr _document;
DocumentSaveHelper* _self;
NSWindow* _window;
};
}
@implementation DocumentSaveHelper
@synthesize saveFolder, userAbort;
- (id)init
{
D(DBF_DocumentController_SaveHelper, bug("\n"););
if(self = [super init])
{
}
return self;
}
+ (void)trySaveDocuments:(std::vector<document::document_ptr> const&)someDocuments forWindow:(NSWindow*)aWindow defaultDirectory:(NSString*)aFolder andCallback:(document_save_callback_t*)aCallback
{
[[[[DocumentSaveHelper alloc] init] autorelease] trySaveDocuments:someDocuments forWindow:aWindow defaultDirectory:aFolder andCallback:aCallback];
}
+ (void)trySaveDocument:(document::document_ptr const&)aDocument forWindow:(NSWindow*)aWindow defaultDirectory:(NSString*)aFolder andCallback:(document_save_callback_t*)aCallback
{
[DocumentSaveHelper trySaveDocuments:std::vector<document::document_ptr>(1, aDocument) forWindow:aWindow defaultDirectory:aFolder andCallback:aCallback];
}
- (void)dealloc
{
D(DBF_DocumentController_SaveHelper, bug("\n"););
[window release];
[saveFolder release];
[super dealloc];
}
- (file::save_context_ptr const&)context { return context; }
- (void)setContext:(file::save_context_ptr const&)newContext { context = newContext; }
- (void)saveNextDocument
{
if(documents.empty())
return;
[self retain]; // keep us retained until document is saved
document::document_ptr document = documents.back();
D(DBF_DocumentController_SaveHelper, bug("%s (%zu total)\n", document->display_name().c_str(), documents.size()););
document->try_save(document::save_callback_ptr((document::save_callback_t*)new save_callback_t(document, self, window)));
}
- (void)trySaveDocuments:(std::vector<document::document_ptr> const&)someDocuments forWindow:(NSWindow*)aWindow defaultDirectory:(NSString*)aFolder andCallback:(document_save_callback_t*)aCallback
{
documents = someDocuments;
window = [aWindow retain];
saveFolder = [aFolder retain];
callback = aCallback;
std::reverse(documents.begin(), documents.end());
[[NSNotificationCenter defaultCenter] postNotificationName:@"OakDocumentNotificationWillSave" object:self];
[self saveNextDocument];
}
- (void)didSaveDocument:(document::document_ptr const&)aDocument success:(BOOL)flag error:(std::string const&)aMessage usingFilter:(oak::uuid_t const&)aFilter
{
D(DBF_DocumentController_SaveHelper, bug("%s, success %s, user abort %s\n", aDocument->path().c_str(), BSTR(flag), BSTR(userAbort)););
if(!flag && !userAbort)
{
[window.attachedSheet orderOut:self];
if(aFilter)
show_command_error(aMessage, aFilter, window);
else [[NSAlert alertWithMessageText:[NSString stringWithCxxString:text::format("The document “%s” could not be saved.", aDocument->display_name().c_str())] defaultButton:@"OK" alternateButton:nil otherButton:nil informativeTextWithFormat:[NSString stringWithCxxString:aMessage] ?: @"Please check Console output for reason."] beginSheetModalForWindow:window modalDelegate:nil didEndSelector:NULL contextInfo:NULL];
}
if(callback)
callback->did_save_document(aDocument, flag, aMessage, aFilter);
documents.pop_back();
if(flag)
[self saveNextDocument];
if(flag && [[window delegate] respondsToSelector:@selector(updateProxyIcon)])
[[window delegate] performSelector:@selector(updateProxyIcon)]; // FIXME The delegate needs to update proxy icon based on “exists on disk” notifications from document_t
[self release];
}
// ===================
// = Sheet Callbacks =
// ===================
- (void)savePanelDidEnd:(OakSavePanel*)sheet path:(NSString*)aPath contextInfo:(void*)info
{
D(DBF_DocumentController_SaveHelper, bug("%s\n", to_s(aPath).c_str()););
if(aPath)
{
documents.back()->set_path(to_s(aPath));
context->set_path(to_s(aPath));
}
else
{
userAbort = YES;
}
context.reset();
}
- (void)makeWritableSheetDidEnd:(NSAlert*)alert returnCode:(NSInteger)returnCode contextInfo:(void*)info
{
D(DBF_DocumentController_SaveHelper, bug("%s\n", BSTR(returnCode == NSAlertDefaultReturn)););
file::save_context_ptr ctxt = context;
[self setContext:file::save_context_ptr()];
if(returnCode == NSAlertDefaultReturn)
ctxt->set_make_writable(true);
else userAbort = YES;
[alert release];
}
- (void)encodingSheetDidEnd:(NSAlert*)alert returnCode:(NSInteger)returnCode contextInfo:(void*)info
{
D(DBF_DocumentController_SaveHelper, bug("\n"););
file::save_context_ptr ctxt = context;
[self setContext:file::save_context_ptr()];
userAbort = returnCode != NSAlertDefaultReturn;
if(!userAbort)
{
OakEncodingPopUpButton* popUp = (OakEncodingPopUpButton*)[alert accessoryView];
std::string encoding = to_s(popUp.encoding);
bool bom = encoding != "UTF-8" && encoding.find("UTF-") == 0;
ctxt->set_encoding(encoding, bom);
}
[alert release];
}
@end

View File

@@ -0,0 +1,83 @@
#import "DocumentController.h"
NSInteger const kSelectDocumentFirst = -1;
NSInteger const kSelectDocumentLast = -2;
NSInteger const kSelectDocumentNone = -3;
struct PUBLIC close_warning_callback_t
{
virtual ~close_warning_callback_t () { }
virtual void will_save_documents () { }
virtual void can_close_documents (bool flag) = 0;
};
@interface DocumentController (Tabs)
- (NSInteger)selectedTabIndex;
- (void)setSelectedTabIndex:(NSInteger)newSelectedTabIndex;
- (document::document_ptr const&)selectedDocument;
- (void)documentDidChange:(document::document_ptr const&)aDocument;
- (void)addDocuments:(std::vector<document::document_ptr> const&)someDocuments andSelect:(NSInteger)selectionConstant closeOther:(BOOL)closeOtherFlag pruneTabBar:(BOOL)pruneTabBarFlag;
- (void)addDocuments:(std::vector<document::document_ptr> const&)someDocuments atIndex:(size_t)insertAt andSelect:(NSInteger)selectionConstant closeOther:(BOOL)closeOtherFlag pruneTabBar:(BOOL)pruneTabBarFlag;
- (void)closeTabsAtIndexes:(NSIndexSet*)anIndexSet quiet:(BOOL)quietFlag;
- (void)showCloseWarningUIForDocuments:(std::vector<document::document_ptr> const&)someDocuments andCallback:(close_warning_callback_t*)aCallback;
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication*)sender;
- (void)updateFileBrowserStatus:(id)sender;
@end
struct document_tab_t
{
document_tab_t (document::document_ptr doc) : _document(doc), _did_open(false)
{
if(_document->is_open())
{
_document->open();
_did_open = true;
}
}
~document_tab_t ()
{
if(_callback)
_document->remove_callback(_callback.get());
if(_did_open)
_document->close();
}
operator document::document_ptr const& () const { return _document; }
struct callback_t : document::document_t::callback_t
{
callback_t (DocumentController* self) : _self(self) { }
void handle_document_event (document::document_ptr document, event_t event)
{
switch(event)
{
case did_change_path:
case did_change_on_disk_status:
case did_change_modified_status:
[_self documentDidChange:document];
break;
}
}
private:
DocumentController* _self;
};
typedef std::tr1::shared_ptr<callback_t> callback_ptr;
void add_callback (DocumentController* self)
{
ASSERT(!_callback);
_callback.reset(new callback_t(self));
_document->add_callback(_callback.get());
}
document::document_ptr _document;
callback_ptr _callback;
bool _did_open;
bool _did_add_callback;
};

View File

@@ -0,0 +1,508 @@
#import "DocumentTabs.h"
#import "DocumentSaveHelper.h"
#import "DocumentCommand.h"
#import <document/document.h>
#import <OakTextView/OakDocumentView.h>
#import <OakFoundation/NSString Additions.h>
#import <OakAppKit/NSAlert Additions.h>
#import <document/collection.h>
#import <document/session.h>
OAK_DEBUG_VAR(DocumentController_Tabs);
@interface DocumentController ()
@property (nonatomic, retain) NSString* windowTitle;
@property (nonatomic, retain) NSString* representedFile;
@property (nonatomic, assign) BOOL isDocumentEdited;
@end
namespace
{
struct callback_info_t
{
callback_info_t (std::vector<document::document_ptr> const& documents, close_warning_callback_t* callback) : documents(documents), callback(callback) { }
std::vector<document::document_ptr> documents;
close_warning_callback_t* callback;
};
}
@implementation DocumentController (Tabs)
- (NSInteger)selectedTabIndex
{
return selectedTabIndex;
}
- (void)setSelectedTabIndex:(NSInteger)newSelectedTabIndex
{
D(DBF_DocumentController_Tabs, bug("%d → %d, window visible: %s\n", (int)selectedTabIndex, (int)newSelectedTabIndex, BSTR([self.window isVisible])););
selectedTabIndex = newSelectedTabIndex;
[tabBarView setSelectedTab:selectedTabIndex];
document::schedule_session_backup();
// This is also set after open succeeds
settings_t const& settings = [self selectedDocument]->settings();
self.windowTitle = [NSString stringWithCxxString:settings.get("windowTitle", [self selectedDocument]->display_name())];
self.representedFile = [NSString stringWithCxxString:[self selectedDocument]->path()];
self.isDocumentEdited = [self selectedDocument]->is_modified();
if(windowHasLoaded)
[[DocumentOpenHelper new] tryOpenDocument:self.selectedDocument forWindow:self.window delegate:self];
}
- (document::document_ptr const&)selectedDocument
{
return *documentTabs[selectedTabIndex];
}
- (void)updateFileBrowserStatus:(id)sender
{
NSMutableArray* openURLs = [NSMutableArray array];
NSMutableArray* modifiedURLs = [NSMutableArray array];
iterate(tab, documentTabs)
{
document::document_ptr document = **tab;
if(document->path() != NULL_STR)
[openURLs addObject:[NSURL fileURLWithPath:[NSString stringWithCxxString:document->path()]]];
if(document->path() != NULL_STR && document->is_modified())
[modifiedURLs addObject:[NSURL fileURLWithPath:[NSString stringWithCxxString:document->path()]]];
}
fileBrowser.openURLs = openURLs;
fileBrowser.modifiedURLs = modifiedURLs;
}
- (void)documentDidChange:(document::document_ptr const&)aDocument
{
D(DBF_DocumentController_Tabs, bug("\n"););
if(*aDocument == *[self selectedDocument])
{
settings_t const& settings = [self selectedDocument]->settings();
self.windowTitle = [NSString stringWithCxxString:settings.get("windowTitle", [self selectedDocument]->display_name())];
self.representedFile = [NSString stringWithCxxString:[self selectedDocument]->path()];
self.isDocumentEdited = [self selectedDocument]->is_modified();
}
[tabBarView reloadData];
[self updateFileBrowserStatus:self];
document::schedule_session_backup();
}
- (void)addDocuments:(std::vector<document::document_ptr> const&)someDocuments atIndex:(size_t)insertAt andSelect:(NSInteger)selectionConstant closeOther:(BOOL)closeOtherFlag pruneTabBar:(BOOL)pruneTabBarFlag
{
D(DBF_DocumentController_Tabs, bug("%zu documents, close other %s, prune %s\n", someDocuments.size(), BSTR(closeOtherFlag), BSTR(pruneTabBarFlag)););
oak::uuid_t selectedDocumentIdentifier;
std::map<oak::uuid_t, document_tab_ptr> oldTabs;
iterate(tab, documentTabs)
{
document::document_ptr document = **tab;
oldTabs.insert(std::make_pair(document->identifier(), *tab));
if(selectedTabIndex == tab - documentTabs.begin())
selectedDocumentIdentifier = document->identifier();
}
std::set<oak::uuid_t> newDocs;
std::vector<document_tab_ptr> wrapped;
iterate(doc, someDocuments)
{
newDocs.insert((*doc)->identifier());
std::map<oak::uuid_t, document_tab_ptr>::const_iterator oldTab = oldTabs.find((*doc)->identifier());
if(oldTab != oldTabs.end())
{
wrapped.push_back(oldTab->second);
}
else
{
wrapped.push_back(document_tab_ptr(new document_tab_t(*doc)));
wrapped.back()->add_callback(self);
}
}
if(closeOtherFlag)
{
std::vector<document_tab_ptr> modifiedTabs;
iterate(tab, documentTabs)
{
document::document_ptr document = **tab;
if(document->is_modified())
modifiedTabs.push_back(*tab);
}
documentTabs.swap(modifiedTabs);
insertAt = 0;
}
else
{
if(scratchDocument)
{
iterate(tab, documentTabs)
{
document::document_ptr document = **tab;
if(document->identifier() == scratchDocument && !document->is_modified() && document->path() == NULL_STR)
newDocs.insert(scratchDocument); // causes scratch document to be closed
}
}
size_t i = documentTabs.size();
while(i-- != 0)
{
document::document_ptr doc = *documentTabs[i];
if(newDocs.find(doc->identifier()) != newDocs.end())
{
documentTabs.erase(documentTabs.begin() + i);
if(i < insertAt)
--insertAt;
}
}
}
documentTabs.insert(documentTabs.begin() + insertAt, wrapped.begin(), wrapped.end());
scratchDocument = oak::uuid_t();
[tabBarView reloadData];
[self updateFileBrowserStatus:self];
switch(selectionConstant)
{
case kSelectDocumentFirst: self.selectedTabIndex = insertAt; break;
case kSelectDocumentLast: self.selectedTabIndex = insertAt + wrapped.size() - 1; break;
case kSelectDocumentNone:
{
for(size_t i = 0; i < documentTabs.size(); ++i)
{
document::document_ptr doc = *documentTabs[i];
if(doc->identifier() == selectedDocumentIdentifier)
self.selectedTabIndex = i;
}
}
break;
default: self.selectedTabIndex = insertAt + selectionConstant; break;
}
if(pruneTabBarFlag)
{
NSInteger excessTabs = documentTabs.size() - tabBarView.countOfVisibleTabs;
if(tabBarView && excessTabs > 0)
{
std::multimap<oak::date_t, size_t> ranked;
for(size_t i = 0; i < documentTabs.size(); ++i)
{
document::document_ptr doc = *documentTabs[i];
if(!doc->is_modified() && doc->is_on_disk() && newDocs.find(doc->identifier()) == newDocs.end())
ranked.insert(std::make_pair(doc->lru(), i));
}
NSMutableIndexSet* indexSet = [NSMutableIndexSet indexSet];
iterate(pair, ranked)
{
[indexSet addIndex:pair->second];
if([indexSet count] == excessTabs)
break;
}
[self closeTabsAtIndexes:indexSet quiet:NO];
}
}
}
- (void)addDocuments:(std::vector<document::document_ptr> const&)someDocuments andSelect:(NSInteger)selectionConstant closeOther:(BOOL)closeOtherFlag pruneTabBar:(BOOL)pruneTabBarFlag
{
[self addDocuments:someDocuments atIndex:(documentTabs.empty() ? 0 : selectedTabIndex + 1) andSelect:selectionConstant closeOther:closeOtherFlag pruneTabBar:pruneTabBarFlag];
}
- (void)closeTabsAtIndexes:(NSIndexSet*)anIndexSet quiet:(BOOL)quietFlag
{
NSInteger newSelectedIndex = selectedTabIndex;
if(quietFlag == NO)
{
std::vector<document::document_ptr> documents;
NSUInteger i = documentTabs.size();
while((i = [anIndexSet indexLessThanIndex:i]) != NSNotFound)
{
document::document_ptr document = *documentTabs[i];
if(document->is_modified())
documents.push_back(document);
}
if(!documents.empty())
{
struct callback_t : close_warning_callback_t
{
callback_t (DocumentController* controller, NSIndexSet* indexSet)
{
_self = [controller retain];
_indexSet = [indexSet retain];
}
~callback_t ()
{
[_self release];
[_indexSet release];
}
void can_close_documents (bool flag)
{
if(flag)
[_self closeTabsAtIndexes:_indexSet quiet:YES];
delete this;
}
private:
DocumentController* _self;
NSIndexSet* _indexSet;
};
return (void)[self showCloseWarningUIForDocuments:documents andCallback:new callback_t(self, anIndexSet)];
}
}
NSUInteger i = documentTabs.size();
while((i = [anIndexSet indexLessThanIndex:i]) != NSNotFound)
{
if(i <= newSelectedIndex && newSelectedIndex)
--newSelectedIndex;
documentTabs.erase(documentTabs.begin() + i);
}
if(documentTabs.empty())
return [self close];
[tabBarView reloadData];
[self updateFileBrowserStatus:self];
self.selectedTabIndex = newSelectedIndex;
}
// ==============================
// = DocumentOpenHelperDelegate =
// ==============================
- (void)documentOpenHelper:(DocumentOpenHelper*)documentOpenHelper didOpenDocument:(document::document_ptr const&)aDocument
{
iterate(tab, documentTabs)
{
if(**tab == aDocument && !(*tab)->_did_open)
{
(*tab)->_document->open();
(*tab)->_did_open = true;
}
}
settings_t const& settings = aDocument->settings();
self.windowTitle = [NSString stringWithCxxString:settings.get("windowTitle", aDocument->display_name())];
self.representedFile = [NSString stringWithCxxString:aDocument->path()];
self.isDocumentEdited = aDocument->is_modified();
[documentView setDocument:aDocument];
[self makeTextViewFirstResponder:self];
[documentOpenHelper release];
}
- (void)documentOpenHelper:(DocumentOpenHelper*)documentOpenHelper failedToOpenDocument:(document::document_ptr const&)aDocument error:(std::string const&)aMessage usingFilter:(oak::uuid_t const&)filterUUID
{
if(filterUUID)
show_command_error(aMessage, filterUUID);
NSMutableIndexSet* set = [NSMutableIndexSet indexSet];
for(size_t i = 0; i < documentTabs.size(); ++i)
{
document::document_ptr document = *documentTabs[i];
if(*document == *aDocument)
[set addIndex:i];
}
[self closeTabsAtIndexes:set quiet:YES];
[documentOpenHelper release];
}
// =======================
// = Save/Close Warnings =
// =======================
- (void)closeWarningDidEnd:(NSAlert*)alert returnCode:(NSInteger)returnCode contextInfo:(callback_info_t*)info
{
switch(returnCode)
{
case NSAlertFirstButtonReturn: /* "Save" */
{
struct callback_t : document_save_callback_t
{
callback_t (close_warning_callback_t* callback, size_t count) : _callback(callback), _count(count) { }
void did_save_document (document::document_ptr document, bool flag, std::string const& message, oak::uuid_t const& filter)
{
if(_callback && (_count == 1 || !flag))
_callback->can_close_documents(flag);
if(--_count == 0 || !flag)
delete this;
}
private:
close_warning_callback_t* _callback;
size_t _count;
};
if(info->callback)
info->callback->will_save_documents();
[DocumentSaveHelper trySaveDocuments:info->documents forWindow:self.window defaultDirectory:self.untitledSavePath andCallback:new callback_t(info->callback, info->documents.size())];
}
break;
case NSAlertSecondButtonReturn: /* "Cancel" */
{
if(info->callback)
info->callback->can_close_documents(false);
}
break;
case NSAlertThirdButtonReturn: /* "Don't Save" */
{
if(info->callback)
info->callback->can_close_documents(true);
}
break;
}
delete info;
[alert release];
}
- (void)showCloseWarningUIForDocuments:(std::vector<document::document_ptr> const&)someDocuments andCallback:(close_warning_callback_t*)aCallback
{
D(DBF_DocumentController_Tabs, bug("%s — %zu documents\n", [self.window.title UTF8String], someDocuments.size()););
if(someDocuments.empty())
{
if(aCallback)
aCallback->can_close_documents(true);
return;
}
[[self.window attachedSheet] orderOut:self];
NSAlert* alert = [[NSAlert alloc] init];
[alert setAlertStyle:NSWarningAlertStyle];
[alert addButtons:@"Save", @"Cancel", @"Dont Save", nil];
if(someDocuments.size() == 1)
{
document::document_ptr document = someDocuments.front();
[alert setMessageText:[NSString stringWithCxxString:text::format("Do you want to save the changes you made in the document “%s”?", document->display_name().c_str())]];
[alert setInformativeText:@"Your changes will be lost if you don't save them."];
}
else
{
std::string body = "";
iterate(document, someDocuments)
body += text::format("• “%s”\n", (*document)->display_name().c_str());
[alert setMessageText:@"Do you want to save documents with changes?"];
[alert setInformativeText:[NSString stringWithCxxString:body]];
}
bool windowModal = true;
if(someDocuments.size() == 1)
{
D(DBF_DocumentController_Tabs, bug("select %s\n", someDocuments.back()->display_name().c_str()););
iterate(tab, documentTabs)
{
document::document_ptr document = **tab;
if(someDocuments.front()->identifier() == document->identifier())
self.selectedTabIndex = tab - documentTabs.begin();
}
}
else
{
std::set<oak::uuid_t> docIdentifiers;
iterate(tab, documentTabs)
{
document::document_ptr document = **tab;
docIdentifiers.insert(document->identifier());
}
iterate(document, someDocuments)
{
if(docIdentifiers.find((*document)->identifier()) == docIdentifiers.end())
windowModal = false;
}
}
if(windowModal)
[alert beginSheetModalForWindow:self.window modalDelegate:self didEndSelector:@selector(closeWarningDidEnd:returnCode:contextInfo:) contextInfo:new callback_info_t(someDocuments, aCallback)];
else [self closeWarningDidEnd:alert returnCode:[alert runModal] contextInfo:new callback_info_t(someDocuments, aCallback)];
}
// ===========================
// = Application Termination =
// ===========================
- (void)shutdownCleanup
{
document::save_session(false);
for(NSWindow* window in [NSApp orderedWindows])
{
DocumentController* delegate = (DocumentController*)[window delegate];
if([delegate isKindOfClass:[DocumentController class]])
{
// it might be better to call [delegate close] but with auto release pools we cant be 100% certain that this closes all documents (in this event loop cycle)
[delegate->documentView setDocument:document::create()];
delegate->documentTabs.clear();
}
}
}
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication*)sender
{
D(DBF_DocumentController_Tabs, bug("%s\n", [self.window.title UTF8String]););
DocumentController* controller = nil;
std::set<oak::uuid_t> docIdentifiers;
std::vector<document::document_ptr> documents;
for(NSWindow* window in [NSApp orderedWindows])
{
DocumentController* delegate = (DocumentController*)[window delegate];
if([delegate isKindOfClass:[DocumentController class]])
{
iterate(tab, delegate->documentTabs)
{
document::document_ptr document = **tab;
if(document->is_modified() && docIdentifiers.find(document->identifier()) == docIdentifiers.end())
{
documents.push_back(document);
docIdentifiers.insert(document->identifier());
if(!controller)
controller = delegate;
}
}
}
}
if(!documents.empty())
{
struct callback_t : close_warning_callback_t
{
callback_t (DocumentController* self) : _self(self), _did_reply(false) { }
void will_save_documents ()
{
[NSApp replyToApplicationShouldTerminate:NO];
_did_reply = true;
}
void can_close_documents (bool flag)
{
if(!_did_reply && flag)
[_self shutdownCleanup];
if(!_did_reply)
[NSApp replyToApplicationShouldTerminate:flag];
else if(flag)
[NSApp terminate:nil];
delete this;
}
private:
DocumentController* _self;
bool _did_reply;
};
return [controller showCloseWarningUIForDocuments:documents andCallback:new callback_t(self)], NSTerminateLater;
}
[self shutdownCleanup];
return NSTerminateNow;
}
@end

View File

@@ -0,0 +1,13 @@
@class OakEncodingPopUpButton;
@interface EncodingViewController : NSViewController
{
IBOutlet OakEncodingPopUpButton* popUpButton;
IBOutlet NSTextView* textView;
char const* first;
char const* last;
}
@property (nonatomic, readonly) NSString* currentEncoding;
- (id)initWithFirst:(char const*)firstPointer last:(char const*)lastPointer;
@end

View File

@@ -0,0 +1,36 @@
#import "EncodingView.h"
#import <OakFoundation/NSString Additions.h>
#import <OakAppKit/OakEncodingPopUpButton.h>
#import <text/hexdump.h>
#import <oak/oak.h>
#import <oak/debug.h>
@implementation EncodingViewController
- (id)initWithFirst:(char const*)firstPointer last:(char const*)lastPointer
{
if(self = [super initWithNibName:@"EncodingView" bundle:[NSBundle bundleForClass:[self class]]])
{
first = firstPointer;
last = lastPointer;
}
return self;
}
- (NSString*)currentEncoding
{
return popUpButton.encoding;
}
- (void)loadView
{
[super loadView];
ASSERT(self.view); ASSERT(popUpButton); ASSERT(textView);
[[textView textStorage] setAttributedString:[[[NSAttributedString alloc] initWithString:[NSString stringWithCxxString:text::to_hex(first, last)] attributes:@{ NSFontAttributeName : [NSFont userFixedPitchFontOfSize:12] }] autorelease]];
[textView setEditable:NO];
int lines = oak::cap(5, (last - first) / 16, 20);
NSSize size = self.view.frame.size;
[self.view setFrameSize:NSMakeSize(size.width, size.height - 300 + 16*lines + 1)];
}
@end

View File

@@ -0,0 +1,66 @@
enum enabled_grammar_t { kEnabledGrammarsRecommended = 0, kEnabledGrammarsInstalled = 1, kEnabledGrammarsAll = 2 };
@protocol FileTypeDialogDelegate;
@interface FileTypeDialog : NSWindowController
{
IBOutlet NSTextField* alertTextField;
IBOutlet NSTextField* infoTextField;
IBOutlet NSTableView* fileTypesTableView;
IBOutlet NSButton* useForAllCheckBox;
IBOutlet NSWindow* installingBundleWindow;
IBOutlet NSTextField* installingBundleActivityTextField;
IBOutlet NSProgressIndicator* installingBundleProgressIndicator;
NSString* path;
std::string firstLine;
NSInteger enabledGrammars;
BOOL persistentSetting;
BOOL canOpenDocument;
NSArray* recommendedGrammars;
NSArray* installedGrammars;
NSArray* allGrammars;
NSArray* grammars;
NSIndexSet* selectedGrammarIndexes;
NSString* alertFormatString;
NSString* infoFormatString;
NSString* useForAllFormatString;
NSWindow* mainWindow;
id <FileTypeDialogDelegate> delegate;
void* contextInfo;
}
@property (nonatomic, retain) NSString* path;
@property (nonatomic, assign) NSInteger enabledGrammars;
@property (nonatomic, assign) BOOL persistentSetting;
@property (nonatomic, assign) BOOL canOpenDocument;
@property (nonatomic, retain) NSArray* recommendedGrammars;
@property (nonatomic, retain) NSArray* installedGrammars;
@property (nonatomic, retain) NSArray* allGrammars;
@property (nonatomic, retain) NSArray* grammars;
@property (nonatomic, retain) NSIndexSet* selectedGrammarIndexes;
@property (nonatomic, retain) NSString* alertFormatString;
@property (nonatomic, retain) NSString* infoFormatString;
@property (nonatomic, retain) NSString* useForAllFormatString;
@property (nonatomic, readonly) NSDictionary* grammar;
@property (nonatomic, readonly) NSString* fileType;
- (id)initWithPath:(NSString*)aPath first:(char const*)firstPointer last:(char const*)lastPointer;
- (void)beginSheetModalForWindow:(NSWindow*)aWindow modalDelegate:(id <FileTypeDialogDelegate>)aDelegate contextInfo:(void*)info;
- (IBAction)performOpenDocument:(id)sender;
- (IBAction)performCancelOperation:(id)sender;
@end
@protocol FileTypeDialogDelegate
- (void)fileTypeDialog:(FileTypeDialog*)fileTypeDialog didSelectFileType:(NSString*)aFileType contextInfo:(void*)info;
@end

View File

@@ -0,0 +1,257 @@
#import "FileTypeDialog.h"
#import <text/ctype.h>
#import <io/path.h>
#import <regexp/format_string.h>
#import <updater/updater.h>
#import <bundles/bundles.h>
#import <file/type.h>
#import <ns/ns.h>
#import <network/network.h>
#import <OakFoundation/NSArray Additions.h>
#import <OakFoundation/NSString Additions.h>
#import <BundlesManager/BundlesManager.h>
#import <oak/CocoaSTL.h>
namespace
{
struct grammar_info_t
{
grammar_info_t (std::string name, std::string scope, oak::uuid_t uuid, oak::uuid_t bundle_uuid) : name(name), scope(scope), uuid(uuid), bundle_uuid(bundle_uuid) { }
std::string name;
std::string scope;
oak::uuid_t uuid;
oak::uuid_t bundle_uuid;
bool operator< (grammar_info_t const& rhs) const
{
return text::less_t()(name, rhs.name);
}
};
}
static NSArray* wrap (std::set<grammar_info_t> const& array)
{
NSMutableArray* res = [NSMutableArray array];
iterate(info, array)
{
NSMutableDictionary* dictionary = [NSMutableDictionary dictionaryWithObjectsAndKeys:
[NSString stringWithCxxString:info->name], @"name",
[NSString stringWithCxxString:info->scope], @"scope",
[NSString stringWithCxxString:info->uuid], @"uuid",
[NSString stringWithCxxString:info->bundle_uuid], @"bundleUUID",
nil];
[res addObject:dictionary];
}
return res;
}
@implementation FileTypeDialog
@synthesize path, enabledGrammars, persistentSetting, canOpenDocument;
@synthesize recommendedGrammars, installedGrammars, allGrammars;
@synthesize grammars, selectedGrammarIndexes;
@synthesize alertFormatString, infoFormatString, useForAllFormatString;
- (id)initWithPath:(NSString*)aPath first:(char const*)firstPointer last:(char const*)lastPointer
{
if(self = [super initWithWindowNibName:@"FileTypeDialog"])
{
self.path = aPath;
firstLine = std::string(firstPointer, std::find(firstPointer, lastPointer, '\n'));
}
return self;
}
- (void)dealloc
{
self.path = nil;
self.recommendedGrammars = nil;
self.installedGrammars = nil;
self.allGrammars = nil;
self.grammars = nil;
self.selectedGrammarIndexes = nil;
self.alertFormatString = nil;
self.infoFormatString = nil;
self.useForAllFormatString = nil;
[super dealloc];
}
- (void)setupGrammars
{
std::set<grammar_info_t> recommended, installed, all;
citerate(item, bundles::query(bundles::kFieldAny, NULL_STR, scope::wildcard, bundles::kItemTypeGrammar))
installed.insert(grammar_info_t((*item)->name(), (*item)->value_for_field(bundles::kFieldGrammarScope), (*item)->uuid(), (*item)->bundle_uuid()));
all = installed;
if(network::can_reach_host("updates.textmate.org"))
{
citerate(bundle, bundles_db::index())
{
citerate(grammar, (*bundle)->grammars())
{
grammar_info_t info((*grammar)->name(), (*grammar)->scope(), (*grammar)->uuid(), (*bundle)->uuid());
all.insert(info);
if((*grammar)->mode_line() != NULL_STR && regexp::search((*grammar)->mode_line(), firstLine.data(), firstLine.data() + firstLine.size()))
{
recommended.insert(info);
}
else
{
iterate(ext, (*grammar)->file_types())
{
if(path::rank(to_s(path), *ext))
recommended.insert(info);
}
}
}
}
}
if(recommended.empty())
{
iterate(info, all)
{
if(info->scope == "text.plain")
recommended.insert(*info);
}
}
self.recommendedGrammars = wrap(recommended);
self.installedGrammars = wrap(installed);
self.allGrammars = wrap(all);
}
- (void)windowDidLoad
{
self.alertFormatString = [alertTextField stringValue];
self.infoFormatString = [infoTextField stringValue];
self.useForAllFormatString = [useForAllCheckBox title];
std::map<std::string, std::string> variables;
variables["DisplayName"] = path::display_name(to_s(path));
std::string const ext = path::extensions(to_s(path));;
if(ext != "")
{
variables["X"] = ext;
self.persistentSetting = YES;
}
std::string const alert = format_string::expand(to_s(alertFormatString), variables);
std::string const info = format_string::expand(to_s(infoFormatString), variables);
std::string const choice = format_string::expand(to_s(useForAllFormatString), variables);
[alertTextField setStringValue:[NSString stringWithCxxString:alert]];
[infoTextField setStringValue:[NSString stringWithCxxString:info]];
[useForAllCheckBox setTitle:[NSString stringWithCxxString:choice]];
}
- (void)setEnabledGrammars:(NSInteger)newFilter
{
enabledGrammars = newFilter;
switch(enabledGrammars)
{
case kEnabledGrammarsRecommended: self.grammars = self.recommendedGrammars; break;
case kEnabledGrammarsInstalled: self.grammars = self.installedGrammars; break;
case kEnabledGrammarsAll: self.grammars = self.allGrammars; break;
}
[fileTypesTableView scrollRowToVisible:[fileTypesTableView selectedRow]];
}
- (NSDictionary*)grammar
{
return [selectedGrammarIndexes count] == 0 ? nil : [grammars objectAtIndex:[selectedGrammarIndexes firstIndex]];
}
- (NSString*)fileType
{
return [self.grammar objectForKey:@"scope"];
}
- (void)beginSheetModalForWindow:(NSWindow*)aWindow modalDelegate:(id <FileTypeDialogDelegate>)aDelegate contextInfo:(void*)info
{
[self setupGrammars];
self.grammars = self.recommendedGrammars;
self.selectedGrammarIndexes = [self.grammars count] == 0 ? [NSIndexSet indexSet] : [NSIndexSet indexSetWithIndex:0];
self.canOpenDocument = YES;
mainWindow = aWindow;
delegate = aDelegate;
contextInfo = info;
[NSApp beginSheet:self.window modalForWindow:aWindow modalDelegate:self didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:NULL];
}
static bool is_installed (oak::uuid_t const& uuid)
{
return bundles::lookup(uuid);
}
- (void)checkIfBundleIsInstalled:(NSTimer*)aTimer
{
NSString* uuid = [aTimer userInfo];
if(is_installed(to_s(uuid)))
{
[aTimer invalidate];
[installingBundleProgressIndicator stopAnimation:self];
[installingBundleProgressIndicator unbind:@"value"];
[installingBundleActivityTextField unbind:@"value"];
[NSApp endSheet:installingBundleWindow];
[installingBundleWindow orderOut:self];
[delegate fileTypeDialog:self didSelectFileType:self.fileType contextInfo:contextInfo];
}
}
- (void)sheetDidEnd:(NSWindow*)aSheet returnCode:(NSInteger)returnCode contextInfo:(void*)unused;
{
self.canOpenDocument = NO;
if(returnCode == NSRunAbortedResponse)
return [delegate fileTypeDialog:self didSelectFileType:nil contextInfo:contextInfo];
NSDictionary* grammar = self.grammar;
std::string scope = to_s((NSString*)[grammar objectForKey:@"scope"]);
oak::uuid_t uuid = to_s((NSString*)[grammar objectForKey:@"uuid"]);
oak::uuid_t bundleUUID = to_s((NSString*)[grammar objectForKey:@"bundleUUID"]);
if(is_installed(uuid))
{
if(self.persistentSetting)
file::set_type(to_s(path), scope);
return [delegate fileTypeDialog:self didSelectFileType:self.fileType contextInfo:contextInfo];
}
citerate(bundle, bundles_db::index())
{
if(bundleUUID == (*bundle)->uuid())
{
[NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(checkIfBundleIsInstalled:) userInfo:[grammar objectForKey:@"uuid"] repeats:YES];
[[BundlesManager sharedInstance] installBundle:*bundle];
[installingBundleActivityTextField bind:@"value" toObject:[BundlesManager sharedInstance] withKeyPath:@"activityText" options:nil];
[installingBundleProgressIndicator bind:@"value" toObject:[BundlesManager sharedInstance] withKeyPath:@"progress" options:nil];
[installingBundleProgressIndicator startAnimation:self];
[NSApp beginSheet:installingBundleWindow modalForWindow:mainWindow modalDelegate:nil didEndSelector:NULL contextInfo:NULL];
return;
}
}
return [delegate fileTypeDialog:self didSelectFileType:nil contextInfo:contextInfo];
}
- (IBAction)performOpenDocument:(id)sender
{
[self.window orderOut:self];
[NSApp endSheet:self.window returnCode:NSRunStoppedResponse];
}
- (IBAction)performCancelOperation:(id)sender
{
[self.window orderOut:self];
[NSApp endSheet:self.window returnCode:NSRunAbortedResponse];
}
@end