mirror of
https://github.com/textmate/textmate.git
synced 2026-04-06 03:01:29 -04:00
Initial commit
This commit is contained in:
11
Frameworks/DocumentWindow/src/DocumentCommand.h
Normal file
11
Frameworks/DocumentWindow/src/DocumentCommand.h
Normal 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 */
|
||||
334
Frameworks/DocumentWindow/src/DocumentCommand.mm
Normal file
334
Frameworks/DocumentWindow/src/DocumentCommand.mm
Normal 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 wasn’t 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];
|
||||
}
|
||||
111
Frameworks/DocumentWindow/src/DocumentController.h
Normal file
111
Frameworks/DocumentWindow/src/DocumentController.h
Normal 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
|
||||
1417
Frameworks/DocumentWindow/src/DocumentController.mm
Normal file
1417
Frameworks/DocumentWindow/src/DocumentController.mm
Normal file
File diff suppressed because it is too large
Load Diff
18
Frameworks/DocumentWindow/src/DocumentOpenHelper.h
Normal file
18
Frameworks/DocumentWindow/src/DocumentOpenHelper.h
Normal 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
|
||||
136
Frameworks/DocumentWindow/src/DocumentOpenHelper.mm
Normal file
136
Frameworks/DocumentWindow/src/DocumentOpenHelper.mm
Normal 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
|
||||
23
Frameworks/DocumentWindow/src/DocumentSaveHelper.h
Normal file
23
Frameworks/DocumentWindow/src/DocumentSaveHelper.h
Normal 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);
|
||||
246
Frameworks/DocumentWindow/src/DocumentSaveHelper.mm
Normal file
246
Frameworks/DocumentWindow/src/DocumentSaveHelper.mm
Normal 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
|
||||
83
Frameworks/DocumentWindow/src/DocumentTabs.h
Normal file
83
Frameworks/DocumentWindow/src/DocumentTabs.h
Normal 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;
|
||||
};
|
||||
508
Frameworks/DocumentWindow/src/DocumentTabs.mm
Normal file
508
Frameworks/DocumentWindow/src/DocumentTabs.mm
Normal 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", @"Don’t 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 can’t 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
|
||||
13
Frameworks/DocumentWindow/src/EncodingView.h
Normal file
13
Frameworks/DocumentWindow/src/EncodingView.h
Normal 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
|
||||
36
Frameworks/DocumentWindow/src/EncodingView.mm
Normal file
36
Frameworks/DocumentWindow/src/EncodingView.mm
Normal 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
|
||||
66
Frameworks/DocumentWindow/src/FileTypeDialog.h
Normal file
66
Frameworks/DocumentWindow/src/FileTypeDialog.h
Normal 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
|
||||
257
Frameworks/DocumentWindow/src/FileTypeDialog.mm
Normal file
257
Frameworks/DocumentWindow/src/FileTypeDialog.mm
Normal 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
|
||||
Reference in New Issue
Block a user