Rework document controller

While I have tried to keep most of the functionality the same (to get functional changes spelled out in the commit history) there are some significant changes:

When you open a folder or document(s) a default project folder will be based on the path(s) opened. This means that as long as you always open the root of your project (via `mate`, `open -a TextMate`, dragging the folder to TextMate, using the favorites (⇧⌘O), open dialog, `txmt:` URL scheme, or what have you), your project no longer require a `.tm_properties` file setting `projectDirectory` for Find in Folder (⇧⌘T), Go to File (⌘T), and similar to work as desired. Additionally the default properties now set a window title that include the project folder’s name (and SCM branch when available), so most projects can now also drop setting `windowTitle`.

Furthermore the file browser (for when opening documents) will default to the project folder rather than the folder set in Preferences (the latter is still used if there is no document, e.g. after ⌘N or ⌃⌘N).

The default project folder, even for opening a single document, means that opening more documents from the same folder (via Finder or perhaps something that uses the same temp folder for the local versions (which `rmate` does)) means that the following documents will open as tabs in the already open window (containing the first one opened). I _think_ this takes care of the common request of having all documents opened appear in new tabs of the frontmost window.

For managing tabs the ‘Merge All Windows’ action now ignores minimized windows. This means that if you wish to only merge a few windows then you can minimize all but those you wish to merge and then perform the action.

This commit should also address issue #117 (closing window’s last document via the file browser closes the entire window). Canceling a file open also no longer causes the entire window to close, but there’s still some work to do in this department (actually backtracking to previous state, which could mean closing the window if the window was opened only to show this document).

Prior to this commit the tab bar would be automatically pruned when overflowing, this is no longer the case, though this functionality should return.
This commit is contained in:
Allan Odgaard
2013-01-08 22:45:22 +01:00
parent 8582b807a2
commit aa59684c7e
5 changed files with 1791 additions and 2041 deletions

View File

@@ -1,6 +1,5 @@
#import "DocumentCommand.h"
#import "DocumentController.h"
#import "DocumentTabs.h"
#import "DocumentSaveHelper.h"
#import <OakAppKit/OakToolTip.h>
#import <OakAppKit/OakAppKit.h>
@@ -8,6 +7,7 @@
#import <BundleEditor/BundleEditor.h>
#import <HTMLOutputWindow/HTMLOutputWindow.h>
#import <OakTextView/OakDocumentView.h>
#import <OakFileBrowser/OakFileBrowser.h>
#import <OakSystem/application.h>
#import <OakSystem/process.h>
#import <command/runner.h>
@@ -21,32 +21,6 @@
#import <text/trim.h>
#import <text/tokenize.h>
@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(self.identifier);
}
if(auto theme = documentView.textView.theme)
{
if(auto themeItem = bundles::lookup(theme->uuid()))
{
if(!themeItem->paths().empty())
env["TM_CURRENT_THEME_PATH"] = themeItem->paths().back();
}
}
}
@end
namespace
{
struct delegate_t : command::delegate_t
@@ -224,11 +198,10 @@ void run (bundle_command_t const& command, ng::buffer_t const& buffer, ng::range
{
if(controller)
{
iterate(tab, controller->documentTabs)
citerate(document, controller.documents)
{
document::document_ptr doc = **tab;
if(doc->is_modified() && doc->path() != NULL_STR)
documentsToSave.push_back(doc);
if((*document)->is_modified() && (*document)->path() != NULL_STR)
documentsToSave.push_back((*document));
}
}
}

View File

@@ -1,96 +1,90 @@
#import <OakFileBrowser/OakFileBrowser.h>
#import <OakAppKit/OakTabBarView.h>
#import <OakTextView/OakDocumentView.h>
#import <oak/debug.h>
#import <plist/uuid.h>
#import <document/document.h>
#import <command/runner.h>
#import <scm/scm.h>
@class ProjectLayoutView;
@class OakFilterWindowController;
@class OakHTMLOutputView;
PUBLIC extern NSString* const OakDocumentWindowWillCloseNotification;
struct document_tab_t;
typedef std::shared_ptr<document_tab_t> document_tab_ptr;
PUBLIC @interface DocumentController : NSObject
@property (nonatomic) NSWindow* window;
namespace bundles { struct item_t; typedef std::shared_ptr<item_t> item_ptr; }
@property (nonatomic) NSString* identifier;
@property (nonatomic) NSString* defaultProjectPath;
@property (nonatomic, readonly) NSString* projectPath; // effectiveProjectPath
PUBLIC @interface DocumentController : NSWindowController <NSWindowDelegate, OakFileBrowserDelegate, OakTabBarViewDelegate, OakTabBarViewDataSource, OakTextViewDelegate>
{
OBJC_WATCH_LEAKS(DocumentController);
@property (nonatomic) std::vector<document::document_ptr> const& documents;
@property (nonatomic) document::document_ptr const& selectedDocument;
@property (nonatomic) NSUInteger selectedTabIndex;
IBOutlet OakTabBarView* tabBarView;
IBOutlet ProjectLayoutView* layoutView;
@property (nonatomic) BOOL fileBrowserVisible;
@property (nonatomic) NSDictionary* fileBrowserHistory;
@property (nonatomic) CGFloat fileBrowserWidth;
OakFileBrowser* fileBrowser;
OakDocumentView* documentView;
OakTextView* textView;
@property (nonatomic) BOOL htmlOutputVisible;
@property (nonatomic) NSSize htmlOutputSize;
OakHTMLOutputView* htmlOutputView;
command::runner_ptr runner;
- (void)showWindow:(id)sender;
- (void)openAndSelectDocument:(document::document_ptr const&)aDocument;
- (void)close;
OakFilterWindowController* filterWindowController;
NSUInteger fileChooserSourceIndex;
scm::info_ptr scmInfo;
scm::callback_t* scmCallback;
// =================
// = Document Tabs =
// =================
@package // FIXME
std::vector<document_tab_ptr> documentTabs;
@protected
size_t selectedTabIndex;
oak::uuid_t scratchDocument;
}
@property (nonatomic, readonly) NSString* identifier;
@property (nonatomic, readonly) NSString* documentPath;
@property (nonatomic, readonly) NSString* fileBrowserPath;
@property (nonatomic, readonly) NSString* projectPath;
@property (nonatomic, readonly) NSString* untitledSavePath;
// Session restore
@property (nonatomic) BOOL fileBrowserVisible;
@property (nonatomic) NSDictionary* fileBrowserHistory;
@property (nonatomic) CGFloat fileBrowserWidth;
@property (nonatomic) NSSize htmlOutputSize;
+ (DocumentController*)controllerForDocument:(document::document_ptr const&)aDocument;
+ (DocumentController*)controllerForPath:(std::string const&)aPath;
+ (DocumentController*)controllerForUUID:(oak::uuid_t const&)aUUID;
- (id)init;
- (void)synchronizeWindowTitle;
- (IBAction)newDocumentInTab:(id)sender;
- (IBAction)moveDocumentToNewWindow:(id)sender; // TODO Move to AppController
- (IBAction)mergeAllWindows:(id)sender; // TODO Move to AppController
- (IBAction)goToFileCounterpart:(id)sender;
- (IBAction)selectNextTab:(id)sender;
- (IBAction)selectPreviousTab:(id)sender;
- (IBAction)takeSelectedTabIndexFrom:(id)sender;
- (IBAction)revealFileInProject:(id)sender;
- (IBAction)revealFileInProjectByExpandingAncestors:(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)closeDocumentWithPath:(NSString*)aPath;
- (void)performBundleItem:(bundles::item_ptr const&)anItem;
- (BOOL)setCommandRunner:(command::runner_ptr const&)aRunner;
- (IBAction)toggleHTMLOutput:(id)sender;
- (IBAction)performCloseTab:(id)sender;
- (IBAction)performCloseSplit:(id)sender;
- (IBAction)performCloseWindow:(id)sender;
- (IBAction)performCloseOtherTabs:(id)sender;
- (IBAction)saveDocument:(id)sender;
- (IBAction)saveDocumentAs:(id)sender;
- (IBAction)saveAllDocuments:(id)sender;
@end
// - (IBAction)revertDocumentToSaved:(id)sender;
@interface DocumentController (ApplicationTermination)
// =============================
// = Opening Auxiliary Windows =
// =============================
- (IBAction)orderFrontFindPanel:(id)sender;
- (IBAction)showSymbolChooser:(id)sender;
- (IBAction)goToFile:(id)sender;
// ==================
// = OakFileBrowser =
// ==================
- (IBAction)toggleFileBrowser:(id)sender;
- (IBAction)revealFileInProject:(id)sender;
- (IBAction)revealFileInProjectByExpandingAncestors:(id)sender;
- (IBAction)goToProjectFolder:(id)sender;
- (IBAction)goBack:(id)sender;
- (IBAction)goForward:(id)sender;
- (IBAction)goToParentFolder:(id)sender;
- (IBAction)goToComputer:(id)sender;
- (IBAction)goToHome:(id)sender;
- (IBAction)goToDesktop:(id)sender;
- (IBAction)goToFavorites:(id)sender;
- (IBAction)goToSCMDataSource:(id)sender;
- (IBAction)orderFrontGoToFolder:(id)sender;
// ==============
// = Legacy API =
// ==============
+ (instancetype)controllerForDocument:(document::document_ptr const&)aDocument;
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication*)sender;
- (void)updateVariables:(std::map<std::string, std::string>&)env;
// Private (used by DocumentCommand.mm)
@property (nonatomic, readonly) NSString* scopeAttributes;
- (NSString*)untitledSavePath;
@end

File diff suppressed because it is too large Load Diff

View File

@@ -1,84 +0,0 @@
#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; }
bool operator== (document::document_ptr const& rhs) const { return _document == rhs; }
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:
__unsafe_unretained DocumentController* _self;
};
typedef std::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

@@ -1,479 +0,0 @@
#import "DocumentTabs.h"
#import "DocumentOpenHelper.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);
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
[self synchronizeWindowTitle];
document::document_ptr documentToOpen = self.selectedDocument;
[[DocumentOpenHelper new] tryOpenDocument:documentToOpen forWindow:self.window completionHandler:^(std::string const& error, oak::uuid_t const& filterUUID){
if(error == NULL_STR)
{
if(documentToOpen != self.selectedDocument)
return;
iterate(tab, documentTabs)
{
if(**tab == documentToOpen && !(*tab)->_did_open)
{
(*tab)->_document->open();
(*tab)->_did_open = true;
}
}
[self synchronizeWindowTitle];
[documentView setDocument:documentToOpen];
[self makeTextViewFirstResponder:self];
}
else
{
if(filterUUID)
show_command_error(error, filterUUID);
NSMutableIndexSet* set = [NSMutableIndexSet indexSet];
for(size_t i = 0; i < documentTabs.size(); ++i)
{
document::document_ptr document = *documentTabs[i];
if(*document == *documentToOpen)
[set addIndex:i];
}
[self closeTabsAtIndexes:set quiet:YES];
}
}];
}
- (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])
[self synchronizeWindowTitle];
[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;
_indexSet = indexSet;
}
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;
}
// =======================
// = 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;
}
- (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 dont 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