Bug Summary

File:BibDocument.m
Location:line 3284, column 65
Description:dead store

Annotated Source Code

1// BibDocument.m
2
3// Created by Michael McCracken on Mon Dec 17 2001.
4/*
5 This software is Copyright (c) 2001-2008
6 Michael O. McCracken. All rights reserved.
7
8 Redistribution and use in source and binary forms, with or without
9 modification, are permitted provided that the following conditions
10 are met:
11
12 - Redistributions of source code must retain the above copyright
13 notice, this list of conditions and the following disclaimer.
14
15 - Redistributions in binary form must reproduce the above copyright
16 notice, this list of conditions and the following disclaimer in
17 the documentation and/or other materials provided with the
18 distribution.
19
20 - Neither the name of Michael O. McCracken nor the names of any
21 contributors may be used to endorse or promote products derived
22 from this software without specific prior written permission.
23
24 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
27 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
28 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
29 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
30 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
31 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
32 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
33 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
34 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35 */
36
37#import "BibDocument.h"
38#import "BDSKOwnerProtocol.h"
39#import "BibItem.h"
40#import "BibAuthor.h"
41#import "BibDocument_DataSource.h"
42#import "BibDocument_Actions.h"
43#import "BibDocumentView_Toolbar.h"
44#import "BDSKAppController.h"
45#import "BDSKStringConstants.h"
46#import "BDSKGroup.h"
47#import "BDSKStaticGroup.h"
48#import "BDSKSearchGroup.h"
49#import "BDSKPublicationsArray.h"
50#import "BDSKGroupsArray.h"
51
52#import "BDSKUndoManager.h"
53#import "MultiplePageView.h"
54#import "BDSKPrintableView.h"
55#import "NSWorkspace_BDSKExtensions.h"
56#import "NSFileManager_BDSKExtensions.h"
57#import "BDSKStringEncodingManager.h"
58#import "BDSKHeaderPopUpButtonCell.h"
59#import "BDSKGroupCell.h"
60#import "BDSKScriptHookManager.h"
61#import "BDSKCountedSet.h"
62#import "BDSKFilterController.h"
63#import "BibDocument_Groups.h"
64#import "BibDocument_Search.h"
65#import "BDSKTableSortDescriptor.h"
66#import "BDSKAlert.h"
67#import "BDSKFieldSheetController.h"
68#import "BDSKPreviewer.h"
69#import "BDSKOverlay.h"
70#import "BDSKEditor.h"
71
72#import "BDSKItemPasteboardHelper.h"
73#import "BDSKMainTableView.h"
74#import "BDSKConverter.h"
75#import "BDSKBibTeXParser.h"
76#import "BDSKStringParser.h"
77
78#import "ApplicationServices/ApplicationServices.h"
79#import "BDSKImagePopUpButton.h"
80#import "BDSKRatingButton.h"
81#import "BDSKSplitView.h"
82#import "BDSKCollapsibleView.h"
83#import "BDSKZoomablePDFView.h"
84#import "BDSKZoomableTextView.h"
85
86#import "BDSKMacroResolver.h"
87#import "BDSKErrorObjectController.h"
88#import "BDSKGroupTableView.h"
89#import "BDSKFileContentSearchController.h"
90#import "NSString_BDSKExtensions.h"
91#import "BDSKStatusBar.h"
92#import "NSArray_BDSKExtensions.h"
93#import "NSTextView_BDSKExtensions.h"
94#import "NSTableView_BDSKExtensions.h"
95#import "NSDictionary_BDSKExtensions.h"
96#import "NSSet_BDSKExtensions.h"
97#import "NSFileManager_BDSKExtendedAttributes.h"
98#import "PDFMetadata.h"
99#import "BDSKSharingServer.h"
100#import "BDSKSharingBrowser.h"
101#import "BDSKTemplate.h"
102#import "BDSKGroupTableView.h"
103#import "BDSKFileContentSearchController.h"
104#import "BDSKTemplateParser.h"
105#import "BDSKTemplateObjectProxy.h"
106#import "NSMenu_BDSKExtensions.h"
107#import "NSWindowController_BDSKExtensions.h"
108#import "NSData_BDSKExtensions.h"
109#import "NSURL_BDSKExtensions.h"
110#import "BDSKShellTask.h"
111#import "NSError_BDSKExtensions.h"
112#import "BDSKColoredBox.h"
113#import "BDSKCustomCiteDrawerController.h"
114#import "NSObject_BDSKExtensions.h"
115#import "BDSKDocumentController.h"
116#import "BDSKFiler.h"
117#import "BibItem_PubMedLookup.h"
118#import "BDSKItemSearchIndexes.h"
119#import "PDFDocument_BDSKExtensions.h"
120#import <FileView/FileView.h>
121#import "BDSKLinkedFile.h"
122#import "NSDate_BDSKExtensions.h"
123#import "BDSKFileMigrationController.h"
124#import "NSViewAnimation_BDSKExtensions.h"
125#import "BDSKDocumentSearch.h"
126#import "NSImage_BDSKExtensions.h"
127
128// these are the same as in Info.plist
129NSString *BDSKBibTeXDocumentType = @"BibTeX Database";
130NSString *BDSKRISDocumentType = @"RIS/Medline File";
131NSString *BDSKMinimalBibTeXDocumentType = @"Minimal BibTeX Database";
132NSString *BDSKLTBDocumentType = @"Amsrefs LTB";
133NSString *BDSKEndNoteDocumentType = @"EndNote XML";
134NSString *BDSKMODSDocumentType = @"MODS XML";
135NSString *BDSKAtomDocumentType = @"Atom XML";
136NSString *BDSKArchiveDocumentType = @"BibTeX and Papers Archive";
137
138NSString *BDSKReferenceMinerStringPboardType = @"CorePasteboardFlavorType 0x57454253";
139NSString *BDSKBibItemPboardType = @"edu.ucsd.mmccrack.bibdesk BibItem pboard type";
140NSString *BDSKWeblocFilePboardType = @"CorePasteboardFlavorType 0x75726C20";
141
142// private keys used for storing window information in xattrs
143static NSString *BDSKMainWindowExtendedAttributeKey = @"net.sourceforge.bibdesk.BDSKDocumentWindowAttributes";
144static NSString *BDSKGroupSplitViewFractionKey = @"BDSKGroupSplitViewFractionKey";
145static NSString *BDSKMainTableSplitViewFractionKey = @"BDSKMainTableSplitViewFractionKey";
146static NSString *BDSKDocumentWindowFrameKey = @"BDSKDocumentWindowFrameKey";
147static NSString *BDSKSelectedPublicationsKey = @"BDSKSelectedPublicationsKey";
148static NSString *BDSKDocumentStringEncodingKey = @"BDSKDocumentStringEncodingKey";
149static NSString *BDSKDocumentScrollPercentageKey = @"BDSKDocumentScrollPercentageKey";
150static NSString *BDSKSelectedGroupsKey = @"BDSKSelectedGroupsKey";
151
152static NSString *BDSKDocumentObservationContext = @"BDSKDocumentObservationContext";
153
154enum {
155 BDSKItemChangedGroupFieldMask = 1,
156 BDSKItemChangedSearchKeyMask = 2,
157 BDSKItemChangedSortKeyMask = 4,
158 BDSKItemChangedFilesMask = 8
159};
160
161@interface BDSKFileViewObject : NSObject {
162 NSURL *URL;
163 NSString *string;
164}
165- (id)initWithURL:(NSURL *)aURL string:(NSString *)aString;
166- (NSURL *)URL;
167- (NSString *)string;
168@end
169
170@implementation BDSKFileViewObject
171
172- (id)initWithURL:(NSURL *)aURL string:(NSString *)aString {
173 if (self = [super init]) {
174 URL = [aURL copy];
175 string = [aString copy];
176 }
177 return self;
178}
179
180- (void)dealloc {
181 [URL release];
182 [string release];
183 [super dealloc];
184}
185
186- (NSURL *)URL { return URL; }
187
188- (NSString *)string { return string; }
189
190@end
191
192
193@interface NSFileWrapper (BDSKExtensions)
194- (NSFileWrapper *)addFileWrapperWithPath:(NSString *)path relativeTo:(NSString *)basePath recursive:(BOOL)recursive;
195@end
196
197@interface NSDocument (BDSKPrivateExtensions)
198// declare a private NSDocument method so we can override it
199- (void)changeSaveType:(id)sender;
200@end
201
202@implementation BibDocument
203
204+ (void)initialize {
205 OBINITIALIZEdo { static BOOL hasBeenInitialized = ( BOOL ) 0 ; [ super initialize
] ; if ( hasBeenInitialized ) return ; hasBeenInitialized = (
BOOL ) 1 ; } while ( 0 ) ;
;
206
207 [NSImage makePreviewDisplayImages];
208}
209
210- (id)init{
211 if(self = [super init]){
212
213 publications = [[BDSKPublicationsArray alloc] initWithCapacity:1];
214 shownPublications = [[NSMutableArray alloc] initWithCapacity:1];
215 groupedPublications = [[NSMutableArray alloc] initWithCapacity:1];
216 groups = [(BDSKGroupsArray *)[BDSKGroupsArray alloc] initWithDocument:self];
217
218 frontMatter = [[NSMutableString alloc] initWithString:@""];
219 documentInfo = [[NSMutableDictionary alloc] initForCaseInsensitiveKeys];
220 macroResolver = [[BDSKMacroResolver alloc] initWithOwner:self];
221
222 BDSKUndoManager *newUndoManager = [[[BDSKUndoManager alloc] init] autorelease];
223 [newUndoManager setDelegate:self];
224 [self setUndoManager:newUndoManager];
225
226 pboardHelper = [[BDSKItemPasteboardHelper alloc] init];
227 [pboardHelper setDelegate:self];
228
229 docState.isDocumentClosed = NO( BOOL ) 0;
230
231 // need to set this for new documents
232 [self setDocumentStringEncoding:[BDSKStringEncodingManager defaultEncoding]];
233
234 // these are set in windowControllerDidLoadNib: from the xattr defaults if available
235 bottomPreviewDisplay = BDSKPreviewDisplayText;
236 bottomPreviewDisplayTemplate = nil( ( void * ) 0 );
237 sidePreviewDisplay = BDSKPreviewDisplayFiles;
238 sidePreviewDisplayTemplate = nil( ( void * ) 0 );
239 tableColumnWidths = nil( ( void * ) 0 );
240 sortKey = nil( ( void * ) 0 );
241 previousSortKey = nil( ( void * ) 0 );
242 sortGroupsKey = nil( ( void * ) 0 );
243 currentGroupField = nil( ( void * ) 0 );
244 docState.sortDescending = NO( BOOL ) 0;
245 docState.sortGroupsDescending = NO( BOOL ) 0;
246 docState.didImport = NO( BOOL ) 0;
247 docState.itemChangeMask = 0;
248 docState.displayMigrationAlert = NO( BOOL ) 0;
249
250 // these are created lazily when needed
251 fileSearchController = nil( ( void * ) 0 );
252 drawerController = nil( ( void * ) 0 );
253 macroWC = nil( ( void * ) 0 );
254 infoWC = nil( ( void * ) 0 );
255 previewer = nil( ( void * ) 0 );
256 toolbarItems = nil( ( void * ) 0 );
257 docState.lastPreviewHeight = 0.0;
258 docState.lastGroupViewWidth = 0.0;
259 docState.lastFileViewWidth = 0.0;
260
261 // these are temporary state variables
262 promiseDragColumnIdentifier = nil( ( void * ) 0 );
263 docState.dragFromExternalGroups = NO( BOOL ) 0;
264 docState.currentSaveOperationType = 0;
265
266 [self registerForNotifications];
267
268 searchIndexes = [[BDSKItemSearchIndexes alloc] init];
269 documentSearch = [[BDSKDocumentSearch alloc] initWithDocument:(id)self];
270 rowToSelectAfterDelete = -1;
271 }
272 return self;
273}
274
275- (void)invalidateSearchFieldCellTimer{
276 // AppKit bug workarounds: NSSearchFieldCell's timer creates a retain cycle after typing in it, so we manually invalidate it when the document is deallocated to avoid leaking the cell and timer. Further, if the insertion point is in the searchfield cell when the window closes, the field editor (and associated text system) and undo manager also leak, so we send -[documentWindow endEditingFor:nil] in windowWillClose:.
277 id timer = [[searchField cell] valueForKey:@"_partialStringTimer"];
278 if (timer && [timer respondsToSelector:@selector(invalidate)]) {
279 [timer invalidate];
280 [[searchField cell] setValue:nil( ( void * ) 0 ) forKey:@"_partialStringTimer"];
281 }
282 [searchField setCell:nil( ( void * ) 0 )];
283}
284
285- (void)dealloc{
286 if ([self undoManager])
287 [[self undoManager] removeAllActions];
288 [[NSNotificationCenter defaultCenter] removeObserver:self];
289 [[NSDistributedNotificationCenter defaultCenter] removeObserver:self];
290 [OFPreference removeObserver:self forPreference:nil( ( void * ) 0 )];
291 // workaround for crash: to reproduce, create empty doc, hit cmd-n for new editor window, then cmd-q to quit, choose "don't save"; this results in an -undoManager message to the dealloced document
292 [publications makeObjectsPerformSelector:@selector(setOwner:) withObject:nil( ( void * ) 0 )];
293 [groups makeObjectsPerformSelector:@selector(setDocument:) withObject:nil( ( void * ) 0 )];
294 [fileSearchController release];
295 [pboardHelper setDelegate:nil( ( void * ) 0 )];
296 [pboardHelper release];
297 [macroResolver release];
298 [publications release];
299 [shownPublications release];
300 [groupedPublications release];
301 [groups release];
302 [shownFiles release];
303 [frontMatter release];
304 [documentInfo release];
305 [drawerController release];
306 [toolbarItems release];
307 [statusBar release];
308 [[tableView enclosingScrollView] release];
309 [previewer release];
310 [bottomPreviewDisplayTemplate release];
311 [sidePreviewDisplayTemplate release];
312 [macroWC release];
313 [infoWC release];
314 [promiseDragColumnIdentifier release];
315 [tableColumnWidths release];
316 [sortKey release];
317 [sortGroupsKey release];
318 [searchGroupViewController release];
319 [webGroupViewController release];
320 [searchIndexes release];
321 [searchButtonController release];
322 [migrationController release];
323 [documentSearch release];
324 [super dealloc];
325}
326
327- (NSString *)windowNibName{
328 return @"BibDocument";
329}
330
331- (void)migrationAlertDidEnd:(BDSKAlert *)alert returnCode:(int)returnCode contextInfo:(void *)unused {
332
333 if ([alert checkValue] == YES( BOOL ) 1)
334 [[NSUserDefaults standardUserDefaults] setBool:YES( BOOL ) 1 forKey:@"BDSKDisableMigrationWarning"];
335
336 if (NSAlertDefaultReturn == returnCode)
337 [self migrateFiles:self];
338}
339
340- (void)showWindows{
341 [super showWindows];
342
343 // some xattr setup has to be done after the window is on-screen
344 NSDictionary *xattrDefaults = [self mainWindowSetupDictionaryFromExtendedAttributes];
345
346 NSData *groupData = [xattrDefaults objectForKey:BDSKSelectedGroupsKey];
347 if ([groupData length])
348 [self selectGroups:[NSKeyedUnarchiver unarchiveObjectWithData:groupData]];
349
350 [self selectItemsForCiteKeys:[xattrDefaults objectForKey:BDSKSelectedPublicationsKey defaultObject:[NSArray array]] selectLibrary:NO( BOOL ) 0];
351 NSPoint scrollPoint = [xattrDefaults pointForKey:BDSKDocumentScrollPercentageKey defaultValue:NSZeroPoint];
352 [[tableView enclosingScrollView] setScrollPositionAsPercentage:scrollPoint];
353
354 // Get the search string keyword if available (Spotlight passes this)
355 NSAppleEventDescriptor *event = [[NSAppleEventManager sharedAppleEventManager] currentAppleEvent];
356 NSString *searchString = [[event descriptorForKeyword:keyAESearchText] stringValue];
357
358 if([event eventID] == kAEOpenDocuments && [NSString isEmptyString:searchString] == NO( BOOL ) 0){
359 // We want to handle open events for our Spotlight cache files differently; rather than setting the search field, we can jump to them immediately since they have richer context. This code gets the path of the document being opened in order to check the file extension.
360 NSString *hfsPath = [[[event descriptorForKeyword:keyAEResult] coerceToDescriptorType:typeFileURL] stringValue];
361
362 // hfsPath will be nil for under some conditions, which seems strange; possibly because I wasn't checking eventID == 'odoc'?
363 if(hfsPath == nil( ( void * ) 0 )) NSLog(@"No path available from event %@ (descriptor %@)", event, [event descriptorForKeyword:keyAEResult]);
364 NSURL *fileURL = (hfsPath == nil( ( void * ) 0 ) ? nil( ( void * ) 0 ) : [(id)CFURLCreateWithFileSystemPath(CFAllocatorGetDefault(), (CFStringRef)hfsPath, kCFURLHFSPathStyle, FALSE0) autorelease]);
365
366 OBPOSTCONDITIONdo { if ( ! ( fileURL != ( ( void * ) 0 ) ) ) OBAssertFailed (
"POSTCONDITION" , "fileURL != nil" , "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/BibDocument.m"
, 366 ) ; } while ( ( BOOL ) 0 )
(fileURL != nil);
367 if(fileURL == nil( ( void * ) 0 ) || [[[NSWorkspace sharedWorkspace] UTIForURL:fileURL] isEqualToUTI:@"net.sourceforge.bibdesk.bdskcache"] == NO( BOOL ) 0){
368 // strip extra search criteria
369 NSRange range = [searchString rangeOfString:@":"];
370 if (range.location != NSNotFound) {
371 range = [searchString rangeOfCharacterFromSet:[NSCharacterSet whitespaceCharacterSet] options:NSBackwardsSearch range:NSMakeRange(0, range.location)];
372 if (range.location != NSNotFound && range.location > 0)
373 searchString = [searchString substringWithRange:NSMakeRange(0, range.location)];
374 }
375 [self selectLibraryGroup:nil( ( void * ) 0 )];
376 [self setSearchString:searchString];
377 }
378 }
379
380 if (docState.displayMigrationAlert) {
381 docState.displayMigrationAlert = NO( BOOL ) 0;
382 // If a single file was migrated, this alert will be shown even if all other BibItems already use BDSKLinkedFile. However, I think that's an edge case, since the user had to manually add that pub in a text editor or by setting the local-url field. Items imported or added in BD will already use BDSKLinkedFile, so this notification won't be posted.
383 NSString *verify = NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Verify"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"Verify", @"button title for migration alert");
384 BDSKAlert *alert = [BDSKAlert alertWithMessageText:NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Local File and URL fields have been automatically converted"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"Local File and URL fields have been automatically converted", @"warning in document")
385 defaultButton:verify
386 alternateButton:NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Later"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"Later", @"")
387 otherButton:nil( ( void * ) 0 )
388 informativeTextWithFormat:NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "These fields are being deprecated. BibDesk now uses a more flexible storage format in place of these fields. Choose \"%@\" to manually verify the conversion and optionally remove the old fields. Conversion can be done at any time from the \"%@\" menu. See the Defaults preferences for more options."
) value : @ "" table : ( ( void * ) 0 ) ]
(@"These fields are being deprecated. BibDesk now uses a more flexible storage format in place of these fields. Choose \"%@\" to manually verify the conversion and optionally remove the old fields. Conversion can be done at any time from the \"%@\" menu. See the Defaults preferences for more options.", @"alert text"), verify, NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Database"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"Database", @"Database main menu title")];
389
390 // @@ Should we show a check button? If the user saves the doc as-is, it'll have local-url and bdsk-file fields in it, and there will be no warning the next time it's opened. Someone who uses a script hook to convert bdsk-file back to local-url won't want to see it, though.
391 [alert setHasCheckButton:YES( BOOL ) 1];
392 [alert setCheckValue:NO( BOOL ) 0];
393 [alert setShowsHelp:YES( BOOL ) 1];
394 [alert setHelpAnchor:@"FileMigration"];
395 [alert beginSheetModalForWindow:[self windowForSheet] modalDelegate:self didEndSelector:@selector(migrationAlertDidEnd:returnCode:contextInfo:) contextInfo:NULL( ( void * ) 0 )];
396 }
397}
398
399static void replaceSplitViewSubview(NSView *view, NSSplitView *splitView, NSInteger i) {
400 NSView *placeholderView = [[splitView subviews] objectAtIndex:i];
401 [view setFrame:[placeholderView frame]];
402 [splitView replaceSubview:placeholderView with:view];
403}
404
405- (void)windowControllerDidLoadNib:(NSWindowController *) aController
406{
407 [super windowControllerDidLoadNib:aController];
408
409 // this is the controller for the main window
410 [aController setShouldCloseDocument:YES( BOOL ) 1];
411
412 // hidden default to remove xattrs; this presently occurs before we use them, but it may need to be earlier at some point
413 if ([[NSUserDefaults standardUserDefaults] boolForKey:@"BDSKRemoveExtendedAttributesFromDocuments"] && [self fileURL]) {
414 [[NSFileManager defaultManager] removeAllExtendedAttributesAtPath:[[self fileURL] path] traverseLink:YES( BOOL ) 1 error:NULL( ( void * ) 0 )];
415 }
416
417 // get document-specific attributes (returns empty dictionary if there are none, so defaultValue works correctly)
418 NSDictionary *xattrDefaults = [self mainWindowSetupDictionaryFromExtendedAttributes];
419 OFPreferenceWrapper *pw = [OFPreferenceWrapper sharedPreferenceWrapper];
420
421 [self setupToolbar];
422
423 replaceSplitViewSubview(bottomPreviewTabView, splitView, 1);
424 replaceSplitViewSubview([[groupTableView enclosingScrollView] superview], groupSplitView, 0);
425 replaceSplitViewSubview([mainBox superview], groupSplitView, 1);
426 replaceSplitViewSubview([sidePreviewTabView superview], groupSplitView, 2);
427
428 // First remove the statusbar if we should, as it affects proper resizing of the window and splitViews
429 [statusBar retain]; // we need to retain, as we might remove it from the window
430 if (![pw boolForKey:BDSKShowStatusBarKey]) {
431 [self toggleStatusBar:nil( ( void * ) 0 )];
432 } else {
433 // make sure they are ordered correctly, mainly for the focus ring
434 [statusBar removeFromSuperview];
435 [[mainBox superview] addSubview:statusBar positioned:NSWindowBelow relativeTo:nil( ( void * ) 0 )];
436 }
437 [statusBar setProgressIndicatorStyle:BDSKProgressIndicatorSpinningStyle];
438 [statusBar setTextOffset:NSMaxX([bottomPreviewButton frame]) - 2.0];
439
440 bottomPreviewDisplay = [xattrDefaults intForKey:BDSKBottomPreviewDisplayKey defaultValue:[pw integerForKey:BDSKBottomPreviewDisplayKey]];
441 bottomPreviewDisplayTemplate = [[xattrDefaults objectForKey:BDSKBottomPreviewDisplayTemplateKey defaultObject:[pw stringForKey:BDSKBottomPreviewDisplayTemplateKey]] retain];
442 sidePreviewDisplay = [xattrDefaults intForKey:BDSKSidePreviewDisplayKey defaultValue:[pw integerForKey:BDSKSidePreviewDisplayKey]];
443 sidePreviewDisplayTemplate = [[xattrDefaults objectForKey:BDSKSidePreviewDisplayTemplateKey defaultObject:[pw stringForKey:BDSKSidePreviewDisplayTemplateKey]] retain];
444
445 bottomTemplatePreviewMenu = [[[NSMenu allocWithZone:[NSMenu menuZone]] init] autorelease];
446 [bottomTemplatePreviewMenu setDelegate:self];
447 [bottomPreviewButton setMenu:bottomTemplatePreviewMenu forSegment:0];
448 [bottomPreviewButton setEnabled:[pw boolForKey:BDSKUsesTeXKey] forSegment:BDSKPreviewDisplayTeX];
449 [bottomPreviewButton selectSegmentWithTag:bottomPreviewDisplay];
450
451 sideTemplatePreviewMenu = [[[NSMenu allocWithZone:[NSMenu menuZone]] init] autorelease];
452 [sideTemplatePreviewMenu setDelegate:self];
453 [sidePreviewButton setMenu:sideTemplatePreviewMenu forSegment:0];
454 [sidePreviewButton selectSegmentWithTag:sidePreviewDisplay];
455
456 // This must also be done before we resize the window and the splitViews
457 [groupCollapsibleView setCollapseEdges:BDSKMinXEdgeMask];
458 [groupCollapsibleView setMinSize:NSMakeSize(56.0, 20.0)];
459 [groupGradientView setUpperColor:[NSColor colorWithCalibratedWhite:0.9 alpha:1.0]];
460 [groupGradientView setLowerColor:[NSColor colorWithCalibratedWhite:0.75 alpha:1.0]];
461
462 // make sure they are ordered correctly, mainly for the focus ring
463 [groupCollapsibleView retain];
464 [groupCollapsibleView removeFromSuperview];
465 [[[groupTableView enclosingScrollView] superview] addSubview:groupCollapsibleView positioned:NSWindowBelow relativeTo:nil( ( void * ) 0 )];
466 [groupCollapsibleView release];
467
468 NSRect frameRect = [xattrDefaults rectForKey:BDSKDocumentWindowFrameKey defaultValue:NSZeroRect];
469
470 [aController setWindowFrameAutosaveNameOrCascade:@"Main Window Frame Autosave" setFrame:frameRect];
471
472 [documentWindow setAutorecalculatesKeyViewLoop:YES( BOOL ) 1];
473 [documentWindow makeFirstResponder:tableView];
474
475 // SplitViews setup
476 [groupSplitView setBlendStyle:BDSKStatusBarBlendStyleMask];
477 [splitView setBlendStyle:BDSKMinBlendStyleMask | BDSKMaxBlendStyleMask];
478
479 // set autosave names first
480 [splitView setPositionAutosaveName:@"OASplitView Position Main Window"];
481 [groupSplitView setPositionAutosaveName:@"OASplitView Position Group Table"];
482 if ([aController windowFrameAutosaveName] == nil( ( void * ) 0 )) {
483 // Only autosave the frames when the window's autosavename is set to avoid inconsistencies
484 [splitView setPositionAutosaveName:nil( ( void * ) 0 )];
485 [groupSplitView setPositionAutosaveName:nil( ( void * ) 0 )];
486 }
487
488 // set previous splitview frames
489 float fract;
490 fract = [xattrDefaults floatForKey:BDSKGroupSplitViewFractionKey defaultValue:-1.0];
491 if (fract >= 0)
492 [groupSplitView setFraction:fract];
493 fract = [xattrDefaults floatForKey:BDSKMainTableSplitViewFractionKey defaultValue:-1.0];
494 if (fract >= 0)
495 [splitView setFraction:fract];
496
497 [mainBox setBackgroundColor:[NSColor controlBackgroundColor]];
498
499 // this might be replaced by the file content tableView
500 [[tableView enclosingScrollView] retain];
501 [[tableView enclosingScrollView] setFrame:[mainView bounds]];
502
503 // TableView setup
504 [tableView removeAllTableColumns];
505
506 tableColumnWidths = [[xattrDefaults objectForKey:BDSKColumnWidthsKey] retain];
507 [tableView setupTableColumnsWithIdentifiers:[xattrDefaults objectForKey:BDSKShownColsNamesKey defaultObject:[pw objectForKey:BDSKShownColsNamesKey]]];
508 sortKey = [[xattrDefaults objectForKey:BDSKDefaultSortedTableColumnKey defaultObject:[pw objectForKey:BDSKDefaultSortedTableColumnKey]] retain];
509 previousSortKey = [sortKey retain];
510 docState.sortDescending = [xattrDefaults boolForKey:BDSKDefaultSortedTableColumnIsDescendingKey defaultValue:[pw boolForKey:BDSKDefaultSortedTableColumnIsDescendingKey]];
511 [tableView setHighlightedTableColumn:[tableView tableColumnWithIdentifier:sortKey]];
512
513 [sortGroupsKey autorelease];
514 sortGroupsKey = [[xattrDefaults objectForKey:BDSKSortGroupsKey defaultObject:[pw objectForKey:BDSKSortGroupsKey]] retain];
515 docState.sortGroupsDescending = [xattrDefaults boolForKey:BDSKSortGroupsDescendingKey defaultValue:[pw boolForKey:BDSKSortGroupsDescendingKey]];
516 [self setCurrentGroupField:[xattrDefaults objectForKey:BDSKCurrentGroupFieldKey defaultObject:[pw objectForKey:BDSKCurrentGroupFieldKey]]];
517
518 [tableView setDoubleAction:@selector(editPubOrOpenURLAction:)];
519 NSArray *dragTypes = [NSArray arrayWithObjects:BDSKBibItemPboardType, BDSKWeblocFilePboardType, BDSKReferenceMinerStringPboardType, NSStringPboardType, NSFilenamesPboardType, NSURLPboardType, nil( ( void * ) 0 )];
520 [tableView registerForDraggedTypes:dragTypes];
521 [groupTableView registerForDraggedTypes:dragTypes];
522
523 [sideFileView setBackgroundColor:[[sideFileView enclosingScrollView] backgroundColor]];
524 [bottomFileView setBackgroundColor:[[bottomFileView enclosingScrollView] backgroundColor]];
525
526 [fileCollapsibleView setCollapseEdges:BDSKMaxXEdgeMask];
527 [fileCollapsibleView setMinSize:NSMakeSize(65.0, 20.0)];
528 [fileGradientView setUpperColor:[NSColor colorWithCalibratedWhite:0.9 alpha:1.0]];
529 [fileGradientView setLowerColor:[NSColor colorWithCalibratedWhite:0.75 alpha:1.0]];
530
531 float iconScale = [xattrDefaults floatForKey:BDSKSideFileViewIconScaleKey defaultValue:[pw floatForKey:BDSKSideFileViewIconScaleKey]];
532 if (iconScale < 0.00001) {
533 [sideFileView setAutoScales:YES( BOOL ) 1];
534 } else {
535 [sideFileView setAutoScales:NO( BOOL ) 0];
536 [sideFileView setIconScale:iconScale];
537 }
538 [sideFileView setAutoScales:YES( BOOL ) 1];
539 [sideFileView addObserver:self forKeyPath:@"iconScale" options:0 context:BDSKDocumentObservationContext];
540
541 iconScale = [xattrDefaults floatForKey:BDSKBottomFileViewIconScaleKey defaultValue:[pw floatForKey:BDSKBottomFileViewIconScaleKey]];
542 if (iconScale < 0.00001) {
543 [bottomFileView setAutoScales:YES( BOOL ) 1];
544 } else {
545 [bottomFileView setAutoScales:NO( BOOL ) 0];
546 [bottomFileView setIconScale:iconScale];
547 }
548 [bottomFileView addObserver:self forKeyPath:@"iconScale" options:0 context:BDSKDocumentObservationContext];
549
550 [(BDSKZoomableTextView *)sidePreviewTextView setScaleFactor:[xattrDefaults floatForKey:BDSKSidePreviewScaleFactorKey defaultValue:1.0]];
551 [(BDSKZoomableTextView *)bottomPreviewTextView setScaleFactor:[xattrDefaults floatForKey:BDSKBottomPreviewScaleFactorKey defaultValue:1.0]];
552
553 // ImagePopUpButtons setup
554 [actionMenuButton setShowsMenuWhenIconClicked:YES( BOOL ) 1];
555 [[actionMenuButton cell] setAltersStateOfSelectedItem:NO( BOOL ) 0];
556 [[actionMenuButton cell] setAlwaysUsesFirstItemAsSelected:NO( BOOL ) 0];
557 [[actionMenuButton cell] setUsesItemFromMenu:NO( BOOL ) 0];
558 [[actionMenuButton cell] setRefreshesMenu:YES( BOOL ) 1];
559 [actionMenuButton setDelegate:self];
560
561 [groupActionMenuButton setShowsMenuWhenIconClicked:YES( BOOL ) 1];
562 [[groupActionMenuButton cell] setAltersStateOfSelectedItem:NO( BOOL ) 0];
563 [[groupActionMenuButton cell] setAlwaysUsesFirstItemAsSelected:NO( BOOL ) 0];
564 [[groupActionMenuButton cell] setUsesItemFromMenu:NO( BOOL ) 0];
565 [[groupActionMenuButton cell] setRefreshesMenu:NO( BOOL ) 0];
566
567 [groupActionButton setArrowImage:nil( ( void * ) 0 )];
568 [groupActionButton setAlternateImage:[NSImage imageNamed:@"GroupAction_Pressed"]];
569 [groupActionButton setShowsMenuWhenIconClicked:YES( BOOL ) 1];
570 [[groupActionButton cell] setAltersStateOfSelectedItem:NO( BOOL ) 0];
571 [[groupActionButton cell] setAlwaysUsesFirstItemAsSelected:NO( BOOL ) 0];
572 [[groupActionButton cell] setUsesItemFromMenu:NO( BOOL ) 0];
573 [[groupActionButton cell] setRefreshesMenu:NO( BOOL ) 0];
574
575 BDSKHeaderPopUpButtonCell *headerCell = (BDSKHeaderPopUpButtonCell *)[groupTableView popUpHeaderCell];
576 [headerCell setAction:@selector(changeGroupFieldAction:)];
577 [headerCell setTarget:self];
578 [headerCell setMenu:[self groupFieldsMenu]];
579 [headerCell setIndicatorImage:[NSImage imageNamed:docState.sortGroupsDescending ? @"NSDescendingSortIndicator" : @"NSAscendingSortIndicator"]];
580 [headerCell setUsesItemFromMenu:NO( BOOL ) 0];
581 [headerCell setTitle:[currentGroupField localizedFieldName]];
582 if([headerCell indexOfItemWithRepresentedObject:currentGroupField] != -1)
583 [headerCell selectItemAtIndex:[headerCell indexOfItemWithRepresentedObject:currentGroupField]];
584 else
585 [headerCell selectItemAtIndex:0];
586
587 // array of BDSKSharedGroup objects and zeroconf support, doesn't do anything when already enabled
588 // we don't do this in appcontroller as we want our data to be loaded
589 if([pw boolForKey:BDSKShouldLookForSharedFilesKey]){
590 if([[BDSKSharingBrowser sharedBrowser] isBrowsing])
591 // force an initial update of the tableview, if browsing is already in progress
592 [self handleSharedGroupsChangedNotification:nil( ( void * ) 0 )];
593 else
594 [[BDSKSharingBrowser sharedBrowser] enableSharedBrowsing];
595 }
596 if([pw boolForKey:BDSKShouldShareFilesKey])
597 [[BDSKSharingServer defaultServer] enableSharing];
598
599 // The UI update from setPublications is too early when loading a new document
600 [self updateSmartGroupsCountAndContent:NO( BOOL ) 0];
601 [self updateCategoryGroupsPreservingSelection:NO( BOOL ) 0];
602
603 [saveTextEncodingPopupButton setEncoding:0];
604
605}
606
607- (BOOL)undoManagerShouldUndoChange:(id)sender{
608 if (![self isDocumentEdited]) {
609 BDSKAlert *alert = [BDSKAlert alertWithMessageText:NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Warning"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"Warning", @"Message in alert dialog")
610 defaultButton:NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Yes" )
value : @ "" table : ( ( void * ) 0 ) ]
(@"Yes", @"Button title")
611 alternateButton:NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "No" ) value
: @ "" table : ( ( void * ) 0 ) ]
(@"No", @"Button title")
612 otherButton:nil( ( void * ) 0 )
613 informativeTextWithFormat:NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "You are about to undo past the last point this file was saved. Do you want to do this?"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"You are about to undo past the last point this file was saved. Do you want to do this?", @"Informative text in alert dialog") ];
614
615 int rv = [alert runSheetModalForWindow:documentWindow];
616 if (rv == NSAlertAlternateReturn)
617 return NO( BOOL ) 0;
618 }
619 return YES( BOOL ) 1;
620}
621
622// this is needed for the BDSKOwner protocol
623- (NSUndoManager *)undoManager {
624 return [super undoManager];
625}
626
627- (BOOL)isMainDocument {
628 return [[[NSDocumentController sharedDocumentController] mainDocument] isEqual:self];
629}
630
631- (void)windowWillClose:(NSNotification *)notification{
632
633 // see comment in invalidateSearchFieldCellTimer
634 if (floor(NSAppKitVersionNumber <= NSAppKitVersionNumber10_4824)) {
635 [documentWindow endEditingFor:nil( ( void * ) 0 )];
636 [self invalidateSearchFieldCellTimer];
637 }
638
639 docState.isDocumentClosed = YES( BOOL ) 1;
640
641 [documentSearch terminate];
642 [fileSearchController terminate];
643
644 if([drawerController isDrawerOpen])
645 [drawerController toggle:nil( ( void * ) 0 )];
646 [self saveSortOrder];
647 [self saveWindowSetupInExtendedAttributesAtURL:[self fileURL] forSave:NO( BOOL ) 0];
648
649 // reset the previewer; don't send [self updatePreviews:] here, as the tableview will be gone by the time the queue posts the notification
650 if([[OFPreferenceWrapper sharedPreferenceWrapper] boolForKey:BDSKUsesTeXKey] &&
651 [[BDSKPreviewer sharedPreviewer] isWindowVisible] &&
652 [self isMainDocument] &&
653 [self numberOfSelectedPubs] != 0)
654 [[BDSKPreviewer sharedPreviewer] updateWithBibTeXString:nil( ( void * ) 0 )];
655
656 [pboardHelper setDelegate:nil( ( void * ) 0 )];
657 [pboardHelper release];
658 pboardHelper = nil( ( void * ) 0 );
659
660 [sideFileView removeObserver:self forKeyPath:@"iconScale"];
661 [sideFileView setDataSource:nil( ( void * ) 0 )];
662 [sideFileView setDelegate:nil( ( void * ) 0 )];
663
664 [bottomFileView removeObserver:self forKeyPath:@"iconScale"];
665 [bottomFileView setDataSource:nil( ( void * ) 0 )];
666 [bottomFileView setDelegate:nil( ( void * ) 0 )];
667
668 // safety call here, in case the pasteboard is retaining the document; we don't want notifications after the window closes, since all the pointers to UI elements will be garbage
669 [[NSNotificationCenter defaultCenter] removeObserver:self];
670}
671
672// returns empty dictionary if no attributes set
673- (NSDictionary *)mainWindowSetupDictionaryFromExtendedAttributes {
674 NSDictionary *dict = nil( ( void * ) 0 );
675 if ([self fileURL]) {
676 dict = [[NSFileManager defaultManager] propertyListFromExtendedAttributeNamed:BDSKMainWindowExtendedAttributeKey atPath:[[self fileURL] path] traverseLink:YES( BOOL ) 1 error:NULL( ( void * ) 0 )];
677 }
678 if (nil( ( void * ) 0 ) == dict)
679 dict = [NSDictionary dictionary];
680 return dict;
681}
682
683- (void)saveWindowSetupInExtendedAttributesAtURL:(NSURL *)anURL forSave:(BOOL)isSave{
684
685 NSString *path = [anURL path];
686 if (path && [[NSUserDefaults standardUserDefaults] boolForKey:@"BDSKDisableDocumentExtendedAttributes"] == NO( BOOL ) 0) {
687
688 // We could set each of these as a separate attribute name on the file, but then we'd need to muck around with prepending net.sourceforge.bibdesk. to each key, and that seems messy.
689 NSMutableDictionary *dictionary = [[self mainWindowSetupDictionaryFromExtendedAttributes] mutableCopy];
690
691 NSString *savedSortKey = nil( ( void * ) 0 );
692 if ([sortKey isEqualToString:BDSKImportOrderString] || [sortKey isEqualToString:BDSKRelevanceString]) {
693 if ([previousSortKey isEqualToString:BDSKImportOrderString] == NO( BOOL ) 0 && [previousSortKey isEqualToString:BDSKRelevanceString] == NO( BOOL ) 0)
694 savedSortKey = previousSortKey;
695 } else {
696 savedSortKey = sortKey;
697 }
698
699 [dictionary setObject:[[[tableView tableColumnIdentifiers] arrayByRemovingObject:BDSKImportOrderString] arrayByRemovingObject:BDSKRelevanceString] forKey:BDSKShownColsNamesKey];
700 [dictionary setObject:[self currentTableColumnWidthsAndIdentifiers] forKey:BDSKColumnWidthsKey];
701 [dictionary setObject:savedSortKey ? savedSortKey : BDSKTitleString forKey:BDSKDefaultSortedTableColumnKey];
702 [dictionary setBoolValue:docState.sortDescending forKey:BDSKDefaultSortedTableColumnIsDescendingKey];
703 [dictionary setObject:sortGroupsKey forKey:BDSKSortGroupsKey];
704 [dictionary setBoolValue:docState.sortGroupsDescending forKey:BDSKSortGroupsDescendingKey];
705 [dictionary setRectValue:[documentWindow frame] forKey:BDSKDocumentWindowFrameKey];
706 [dictionary setFloatValue:[groupSplitView fraction] forKey:BDSKGroupSplitViewFractionKey];
707 // of the 3 splitviews, the fraction of the first divider would be considered, so fallback to the fraction from the nib
708 if (NO( BOOL ) 0 == [self hasWebGroupSelected])
709 [dictionary setFloatValue:[splitView fraction] forKey:BDSKMainTableSplitViewFractionKey];
710 [dictionary setObject:currentGroupField forKey:BDSKCurrentGroupFieldKey];
711
712 // if this isn't a save operation, the encoding in xattr is already correct, while our encoding might be different from the actual file encoding, if the user might ignored an encoding warning without saving
713 if(isSave)
714 [dictionary setUnsignedIntValue:[self documentStringEncoding] forKey:BDSKDocumentStringEncodingKey];
715
716 // encode groups so we can select them later with isEqual: (saving row indexes would not be as reliable)
717 [dictionary setObject:([self hasExternalGroupsSelected] ? [NSData data] : [NSKeyedArchiver archivedDataWithRootObject:[self selectedGroups]]) forKey:BDSKSelectedGroupsKey];
718
719 NSArray *selectedKeys = [[self selectedPublications] arrayByPerformingSelector:@selector(citeKey)];
720 if ([selectedKeys count] == 0 || [self hasExternalGroupsSelected])
721 selectedKeys = [NSArray array];
722 [dictionary setObject:selectedKeys forKey:BDSKSelectedPublicationsKey];
723 [dictionary setPointValue:[[tableView enclosingScrollView] scrollPositionAsPercentage] forKey:BDSKDocumentScrollPercentageKey];
724
725 [dictionary setIntValue:bottomPreviewDisplay forKey:BDSKBottomPreviewDisplayKey];
726 [dictionary setObject:bottomPreviewDisplayTemplate forKey:BDSKBottomPreviewDisplayTemplateKey];
727 [dictionary setIntValue:sidePreviewDisplay forKey:BDSKSidePreviewDisplayKey];
728 [dictionary setObject:sidePreviewDisplayTemplate forKey:BDSKSidePreviewDisplayTemplateKey];
729
730 [dictionary setFloatValue:[bottomFileView autoScales] ? 0.0 : [bottomFileView iconScale] forKey:BDSKBottomFileViewIconScaleKey];
731 [dictionary setFloatValue:[sideFileView autoScales] ? 0.0 : [sideFileView iconScale] forKey:BDSKSideFileViewIconScaleKey];
732
733 [dictionary setFloatValue:[(BDSKZoomableTextView *)bottomPreviewTextView scaleFactor] forKey:BDSKBottomPreviewScaleFactorKey];
734 [dictionary setFloatValue:[(BDSKZoomableTextView *)sidePreviewTextView scaleFactor] forKey:BDSKSidePreviewScaleFactorKey];
735
736 if(previewer){
737 [dictionary setFloatValue:[previewer PDFScaleFactor] forKey:BDSKPreviewPDFScaleFactorKey];
738 [dictionary setFloatValue:[previewer RTFScaleFactor] forKey:BDSKPreviewRTFScaleFactorKey];
739 }
740
741 if(fileSearchController){
742 [dictionary setObject:[fileSearchController sortDescriptorData] forKey:BDSKFileContentSearchSortDescriptorKey];
743 }
744
745 NSError *error;
746
747 if ([[NSFileManager defaultManager] setExtendedAttributeNamed:BDSKMainWindowExtendedAttributeKey
748 toPropertyListValue:dictionary
749 atPath:path options:nil( ( void * ) 0 ) error:&error] == NO( BOOL ) 0) {
750 NSLog(@"%@: %@", self, error);
751 }
752
753 [dictionary release];
754 }
755}
756
757#pragma mark -
758#pragma mark Publications acessors
759
760- (void)setPublicationsWithoutUndo:(NSArray *)newPubs{
761 [publications makeObjectsPerformSelector:@selector(setOwner:) withObject:nil( ( void * ) 0 )];
762 [publications setArray:newPubs];
763 [publications makeObjectsPerformSelector:@selector(setOwner:) withObject:self];
764
765 [searchIndexes resetWithPublications:newPubs];
766}
767
768- (void)setPublications:(NSArray *)newPubs{
769 if(newPubs != publications){
770 NSUndoManager *undoManager = [self undoManager];
771 [[undoManager prepareWithInvocationTarget:self] setPublications:publications];
772
773 [self setPublicationsWithoutUndo:newPubs];
774
775 NSDictionary *notifInfo = [NSDictionary dictionaryWithObjectsAndKeys:newPubs, @"pubs", nil( ( void * ) 0 )];
776 [[NSNotificationCenter defaultCenter] postNotificationName:BDSKDocSetPublicationsNotification
777 object:self
778 userInfo:notifInfo];
779 }
780}
781
782- (BDSKPublicationsArray *) publications{
783 return publications;
784}
785
786- (NSArray *) shownPublications{
787 return shownPublications;
788}
789
790- (void)insertPublications:(NSArray *)pubs atIndexes:(NSIndexSet *)indexes{
791 // this assertion is only necessary to preserve file order for undo
792 NSParameterAssertdo { if ( ! ( ( [ indexes count ] == [ pubs count ] ) ) ) { [
[ NSAssertionHandler currentHandler ] handleFailureInMethod :
_cmd object : self file : [ NSString stringWithUTF8String : "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/BibDocument.m"
] lineNumber : 792 description : ( @ "Invalid parameter not satisfying: %s"
) , ( "[indexes count] == [pubs count]" ) , ( 0 ) , ( 0 ) , (
0 ) , ( 0 ) ] ; } } while ( 0 )
([indexes count] == [pubs count]);
793 [[[self undoManager] prepareWithInvocationTarget:self] removePublicationsAtIndexes:indexes];
794
795 [publications insertObjects:pubs atIndexes:indexes];
796
797 [pubs makeObjectsPerformSelector:@selector(setOwner:) withObject:self];
798
799 [searchIndexes addPublications:pubs];
800
801 NSDictionary *notifInfo = [NSDictionary dictionaryWithObjectsAndKeys:pubs, @"pubs", [pubs arrayByPerformingSelector:@selector(searchIndexInfo)], @"searchIndexInfo", nil( ( void * ) 0 )];
802 [[NSNotificationCenter defaultCenter] postNotificationName:BDSKDocAddItemNotification
803 object:self
804 userInfo:notifInfo];
805}
806
807- (void)insertPublication:(BibItem *)pub atIndex:(unsigned int)idx {
808 [self insertPublications:[NSArray arrayWithObject:pub] atIndexes:[NSIndexSet indexSetWithIndex:idx]];
809}
810
811- (void)addPublications:(NSArray *)pubs{
812 [self insertPublications:pubs atIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0,[pubs count])]];
813}
814
815- (void)addPublication:(BibItem *)pub{
816 [self insertPublication:pub atIndex:0]; // insert new pubs at the beginning, so item number is handled properly
817}
818
819- (void)removePublicationsAtIndexes:(NSIndexSet *)indexes{
820 NSArray *pubs = [publications objectsAtIndexes:indexes];
821 [[[self undoManager] prepareWithInvocationTarget:self] insertPublications:pubs atIndexes:indexes];
822
823 NSDictionary *notifInfo = [NSDictionary dictionaryWithObjectsAndKeys:pubs, @"pubs", nil( ( void * ) 0 )];
824 [[NSNotificationCenter defaultCenter] postNotificationName:BDSKDocWillRemoveItemNotification
825 object:self
826 userInfo:notifInfo];
827
828 [[groups lastImportGroup] removePublicationsInArray:pubs];
829 [[groups staticGroups] makeObjectsPerformSelector:@selector(removePublicationsInArray:) withObject:pubs];
830 [searchIndexes removePublications:pubs];
831
832 [publications removeObjectsAtIndexes:indexes];
833
834 [pubs makeObjectsPerformSelector:@selector(setOwner:) withObject:nil( ( void * ) 0 )];
835 [[NSFileManager defaultManager] removeSpotlightCacheFilesForCiteKeys:[pubs arrayByPerformingSelector:@selector(citeKey)]];
836
837 notifInfo = [NSDictionary dictionaryWithObjectsAndKeys:pubs, @"pubs", [pubs arrayByPerformingSelector:@selector(searchIndexInfo)], @"searchIndexInfo", nil( ( void * ) 0 )];
838 [[NSNotificationCenter defaultCenter] postNotificationName:BDSKDocDelItemNotification
839 object:self
840 userInfo:notifInfo];
841}
842
843- (void)removePublications:(NSArray *)pubs{
844 [self removePublicationsAtIndexes:[publications indexesOfObjectsIdenticalTo:pubs]];
845}
846
847- (void)removePublication:(BibItem *)pub{
848 NSIndexSet *indexes = [NSIndexSet indexSetWithIndex:[publications indexOfObjectIdenticalTo:pub]];
849 [self removePublicationsAtIndexes:indexes];
850}
851
852#pragma mark Groups accessors
853
854- (BDSKGroupsArray *)groups{
855 return groups;
856}
857
858#pragma mark Searching
859
860- (BDSKItemSearchIndexes *)searchIndexes{
861 return searchIndexes;
862}
863
864#pragma mark -
865
866- (void)getCopyOfPublicationsOnMainThread:(NSMutableArray *)dstArray{
867 if([NSThread inMainThread] == NO( BOOL ) 0){
868 [self performSelectorOnMainThread:_cmd withObject:dstArray waitUntilDone:YES( BOOL ) 1];
869 } else {
870 NSArray *array = [[NSArray alloc] initWithArray:[self publications] copyItems:YES( BOOL ) 1];
871 [dstArray addObjectsFromArray:array];
872 [array release];
873 }
874}
875
876- (void)getCopyOfMacrosOnMainThread:(NSMutableDictionary *)dstDict{
877 if([NSThread inMainThread] == NO( BOOL ) 0){
878 [self performSelectorOnMainThread:_cmd withObject:dstDict waitUntilDone:YES( BOOL ) 1];
879 } else {
880 NSDictionary *dict = [[NSDictionary alloc] initWithDictionary:[macroResolver macroDefinitions] copyItems:YES( BOOL ) 1];
881 [dstDict addEntriesFromDictionary:dict];
882 [dict release];
883 }
884}
885
886#pragma mark Document Info
887
888- (NSDictionary *)documentInfo{
889 return documentInfo;
890}
891
892- (void)setDocumentInfoWithoutUndo:(NSDictionary *)dict{
893 [documentInfo setDictionary:dict];
894}
895
896- (void)setDocumentInfo:(NSDictionary *)dict{
897 [[[self undoManager] prepareWithInvocationTarget:self] setDocumentInfo:[[documentInfo copy] autorelease]];
898 [documentInfo setDictionary:dict];
899}
900
901- (NSString *)documentInfoForKey:(NSString *)key{
902 return [documentInfo valueForKey:key];
903}
904
905- (id)valueForUndefinedKey:(NSString *)key{
906 return [self documentInfoForKey:key];
907}
908
909- (NSString *)documentInfoString{
910 NSMutableString *string = [NSMutableString stringWithString:@"@bibdesk_info{document_info"];
911 NSEnumerator *keyEnum = [documentInfo keyEnumerator];
912 NSString *key;
913
914 while (key = [keyEnum nextObject])
915 [string appendStrings:@",\n\t", key, @" = ", [[self documentInfoForKey:key] stringAsBibTeXString], nil( ( void * ) 0 )];
916 [string appendString:@"\n}\n"];
917
918 return string;
919}
920
921#pragma mark Macro stuff
922
923- (BDSKMacroResolver *)macroResolver{
924 return macroResolver;
925}
926
927#pragma mark -
928#pragma mark Document Saving
929
930+ (NSArray *)writableTypes
931{
932 NSMutableArray *writableTypes = [[[super writableTypes] mutableCopy] autorelease];
933 [writableTypes addObjectsFromArray:[BDSKTemplate allStyleNames]];
934 return writableTypes;
935}
936
937- (NSString *)fileNameExtensionForType:(NSString *)typeName saveOperation:(NSSaveOperationType)saveOperation
938{
939 // this will never be called on 10.4, so we can safely call super
940 NSString *fileExtension = [super fileNameExtensionForType:typeName saveOperation:saveOperation];
941 if(fileExtension == nil( ( void * ) 0 ))
942 fileExtension = [[BDSKTemplate templateForStyle:typeName] fileExtension];
943 return fileExtension;
944}
945
946#define SAVE_ENCODING_VIEW_OFFSET 30.0
947#define SAVE_FORMAT_POPUP_OFFSET 31.0
948
949static NSPopUpButton *popUpButtonSubview(NSView *view)
950{
951 if ([view isKindOfClass:[NSPopUpButton class]])
952 return (NSPopUpButton *)view;
953
954 NSEnumerator *viewEnum = [[view subviews] objectEnumerator];
955 NSView *subview;
956 NSPopUpButton *popup;
957
958 while (subview = [viewEnum nextObject]) {
959 if (popup = popUpButtonSubview(subview))
960 return popup;
961 }
962 return nil( ( void * ) 0 );
963}
964
965// if the user is saving in one of our plain text formats, give them an encoding option as well
966// this also requires overriding saveToURL:ofType:forSaveOperation:error:
967// to set the document's encoding before writing to the file
968- (BOOL)prepareSavePanel:(NSSavePanel *)savePanel{
969 if([super prepareSavePanel:savePanel] == NO( BOOL ) 0)
970 return NO( BOOL ) 0;
971
972 NSView *accessoryView = [savePanel accessoryView];
973 NSPopUpButton *saveFormatPopupButton = popUpButtonSubview(accessoryView);
974 OBASSERTdo { if ( ! ( saveFormatPopupButton != ( ( void * ) 0 ) ) ) OBAssertFailed
( "ASSERT" , "saveFormatPopupButton != nil" , "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/BibDocument.m"
, 974 ) ; } while ( ( BOOL ) 0 )
(saveFormatPopupButton != nil);
975 NSRect popupFrame = [saveTextEncodingPopupButton frame];
976 popupFrame.origin.y += SAVE_FORMAT_POPUP_OFFSET31.0;
977 [saveFormatPopupButton setFrame:popupFrame];
978 [saveAccessoryView addSubview:saveFormatPopupButton];
979 NSRect savFrame = [saveAccessoryView frame];
980 savFrame.size.width = NSWidth([accessoryView frame]);
981
982 if(NSSaveToOperation == docState.currentSaveOperationType){
983 NSRect exportFrame = [exportAccessoryView frame];
984 savFrame.origin = NSMakePoint(0.0, SAVE_ENCODING_VIEW_OFFSET30.0);
985 [saveAccessoryView setFrame:savFrame];
986 exportFrame.size.width = NSWidth(savFrame);
987 [exportAccessoryView setFrame:exportFrame];
988 [exportAccessoryView addSubview:saveAccessoryView];
989 accessoryView = exportAccessoryView;
990 }else{
991 [saveAccessoryView setFrame:savFrame];
992 accessoryView = saveAccessoryView;
993 }
994 [savePanel setAccessoryView:accessoryView];
995
996 // set the popup to reflect the document's present string encoding
997 [saveTextEncodingPopupButton setEncoding:[self documentStringEncoding]];
998 [saveTextEncodingPopupButton setEnabled:YES( BOOL ) 1];
999
1000 [exportSelectionCheckButton setState:NSOffState];
1001 if(NSSaveToOperation == docState.currentSaveOperationType){
1002 [exportSelectionCheckButton setEnabled:[self numberOfSelectedPubs] > 0 || [self hasLibraryGroupSelected] == NO( BOOL ) 0];
1003 }
1004 [accessoryView setNeedsDisplay:YES( BOOL ) 1];
1005
1006 return YES( BOOL ) 1;
1007}
1008
1009// this is a private method, the action of the file format poup
1010- (void)changeSaveType:(id)sender{
1011 NSSet *typesWithEncoding = [NSSet setWithObjects:BDSKBibTeXDocumentType, BDSKRISDocumentType, BDSKMinimalBibTeXDocumentType, BDSKLTBDocumentType, BDSKArchiveDocumentType, nil( ( void * ) 0 )];
1012 NSString *selectedType = [[sender selectedItem] representedObject];
1013 [saveTextEncodingPopupButton setEnabled:[typesWithEncoding containsObject:selectedType]];
1014 if ([NSDocument instancesRespondToSelector:@selector(changeSaveType:)])
1015 [super changeSaveType:sender];
1016}
1017
1018- (void)runModalSavePanelForSaveOperation:(NSSaveOperationType)saveOperation delegate:(id)delegate didSaveSelector:(SEL)didSaveSelector contextInfo:(void *)contextInfo {
1019 // Override so we can determine if this is a save, saveAs or export operation, so we can prepare the correct accessory view
1020 docState.currentSaveOperationType = saveOperation;
1021 [super runModalSavePanelForSaveOperation:saveOperation delegate:delegate didSaveSelector:didSaveSelector contextInfo:contextInfo];
1022}
1023
1024- (BOOL)saveToURL:(NSURL *)absoluteURL ofType:(NSString *)typeName forSaveOperation:(NSSaveOperationType)saveOperation error:(NSError **)outError{
1025
1026 // Set the string encoding according to the popup.
1027 // NB: the popup has the incorrect encoding if it wasn't displayed, for example for the Save action and saving using AppleScript, so don't reset encoding unless we're actually modifying this document through a menu .
1028 if (NSSaveAsOperation == saveOperation && [saveTextEncodingPopupButton encoding] != 0)
1029 [self setDocumentStringEncoding:[saveTextEncodingPopupButton encoding]];
1030
1031 saveTargetURL = [absoluteURL copy];
1032
1033 BOOL success = [super saveToURL:absoluteURL ofType:typeName forSaveOperation:saveOperation error:outError];
1034
1035 // set com.apple.TextEncoding for other apps
1036 NSString *UTI = [[NSWorkspace sharedWorkspace] UTIForURL:absoluteURL];
1037 if (success && UTI && UTTypeConformsTo((CFStringRef)UTI, kUTTypePlainText))
1038 [[NSFileManager defaultManager] setAppleStringEncoding:[self documentStringEncoding] atPath:[absoluteURL path] error:NULL( ( void * ) 0 )];
1039
1040 [saveTargetURL release];
1041 saveTargetURL = nil( ( void * ) 0 );
1042
1043 // reset the encoding popup so we know when it wasn't shown to the user next time
1044 [saveTextEncodingPopupButton setEncoding:0];
1045 [exportSelectionCheckButton setState:NSOffState];
1046
1047 if(success == NO( BOOL ) 0)
1048 return NO( BOOL ) 0;
1049
1050 if(saveOperation == NSSaveToOperation){
1051 // write template accessory files if necessary
1052 BDSKTemplate *selectedTemplate = [BDSKTemplate templateForStyle:typeName];
1053 if(selectedTemplate){
1054 NSEnumerator *accessoryFileEnum = [[selectedTemplate accessoryFileURLs] objectEnumerator];
1055 NSURL *accessoryURL = nil( ( void * ) 0 );
1056 NSURL *destDirURL = [absoluteURL URLByDeletingLastPathComponent];
1057 while(accessoryURL = [accessoryFileEnum nextObject]){
1058 [[NSFileManager defaultManager] copyObjectAtURL:accessoryURL toDirectoryAtURL:destDirURL error:NULL( ( void * ) 0 )];
1059 }
1060 }
1061
1062 // save our window setup if we export to BibTeX or RIS
1063 if([[self class] isNativeType:typeName] || [typeName isEqualToString:BDSKMinimalBibTeXDocumentType])
1064 [self saveWindowSetupInExtendedAttributesAtURL:absoluteURL forSave:YES( BOOL ) 1];
1065
1066 }else if(saveOperation == NSSaveOperation || saveOperation == NSSaveAsOperation){
1067 [[BDSKScriptHookManager sharedManager] runScriptHookWithName:BDSKSaveDocumentScriptHookName
1068 forPublications:publications
1069 document:self];
1070
1071 // rebuild metadata cache for this document whenever we save
1072 NSEnumerator *pubsE = [[self publications] objectEnumerator];
1073 NSMutableArray *pubsInfo = [[NSMutableArray alloc] initWithCapacity:[publications count]];
1074 BibItem *anItem;
1075 NSDictionary *info;
1076 BOOL update = (saveOperation == NSSaveOperation); // for saveTo we should update all items, as our path changes
1077
1078 while(anItem = [pubsE nextObject]){
1079 OMNI_POOL_STARTdo { NSAutoreleasePool * __pool ; __pool = [ [ NSAutoreleasePool
alloc ] init ] ; @ try {
{
1080 if(info = [anItem metadataCacheInfoForUpdate:update])
1081 [pubsInfo addObject:info];
1082 } OMNI_POOL_END} @ catch ( NSException * __exc ) { [ __exc retain ] ; [ __pool
release ] ; __pool = ( ( void * ) 0 ) ; [ __exc autorelease ]
; [ __exc raise ] ; } @ finally { [ __pool release ] ; } } while
( 0 )
;
1083 }
1084
1085 NSDictionary *infoDict = [[NSDictionary alloc] initWithObjectsAndKeys:pubsInfo, @"publications", absoluteURL, @"fileURL", nil( ( void * ) 0 )];
1086 [pubsInfo release];
1087 [[NSApp delegate] rebuildMetadataCache:infoDict];
1088 [infoDict release];
1089
1090 // save window setup to extended attributes, so it is set also if we use saveAs
1091 [self saveWindowSetupInExtendedAttributesAtURL:absoluteURL forSave:YES( BOOL ) 1];
1092 }
1093
1094 return YES( BOOL ) 1;
1095}
1096
1097- (BOOL)writeToURL:(NSURL *)absoluteURL
1098 ofType:(NSString *)typeName
1099 forSaveOperation:(NSSaveOperationType)saveOperation
1100originalContentsURL:(NSURL *)absoluteOriginalContentsURL
1101 error:(NSError **)outError {
1102 // Override so we can determine if this is an autosave in writeToURL:ofType:error:.
1103 // This is necessary on 10.4 to keep from calling the clearChangeCount hack for an autosave, which incorrectly marks the document as clean.
1104 docState.currentSaveOperationType = saveOperation;
1105 return [super writeToURL:absoluteURL ofType:typeName forSaveOperation:saveOperation originalContentsURL:absoluteOriginalContentsURL error:outError];
1106}
1107
1108- (BOOL)writeToURL:(NSURL *)fileURL ofType:(NSString *)docType error:(NSError **)outError{
1109
1110 BOOL success = YES( BOOL ) 1;
1111 NSError *nsError = nil( ( void * ) 0 );
1112 NSArray *items = publications;
1113
1114 // first we make sure all edits are committed
1115 [[NSNotificationCenter defaultCenter] postNotificationName:BDSKFinalizeChangesNotification
1116 object:self
1117 userInfo:[NSDictionary dictionary]];
1118
1119 if(docState.currentSaveOperationType == NSSaveToOperation && [exportSelectionCheckButton state] == NSOnState)
1120 items = [self numberOfSelectedPubs] > 0 ? [self selectedPublications] : groupedPublications;
1121
1122 if ([docType isEqualToString:BDSKArchiveDocumentType] || [docType isEqualToUTI:[[NSWorkspace sharedWorkspace] UTIForPathExtension:@"tgz"]]) {
1123 success = [self writeArchiveToURL:fileURL forPublications:items error:outError];
1124 } else {
1125 NSFileWrapper *fileWrapper = [self fileWrapperOfType:docType forPublications:items error:&nsError];
1126 success = nil( ( void * ) 0 ) == fileWrapper ? NO( BOOL ) 0 : [fileWrapper writeToFile:[fileURL path] atomically:NO( BOOL ) 0 updateFilenames:NO( BOOL ) 0];
1127 }
1128
1129 // see if this is our error or Apple's
1130 if (NO( BOOL ) 0 == success && [nsError isLocalError]) {
1131
1132 // get offending BibItem if possible
1133 BibItem *theItem = [nsError valueForKey:BDSKUnderlyingItemErrorKey];
1134 if (theItem)
1135 [self selectPublication:theItem];
1136
1137 NSString *errTitle = NSAutosaveOperation == docState.currentSaveOperationType ? NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Unable to autosave file"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"Unable to autosave file", @"Error description") : NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Unable to save file"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"Unable to save file", @"Error description");
1138
1139 // @@ do this in fileWrapperOfType:forPublications:error:? should just use error localizedDescription
1140 NSString *errMsg = [nsError valueForKey:NSLocalizedRecoverySuggestionErrorKey];
1141 if (nil( ( void * ) 0 ) == errMsg)
1142 errMsg = NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "The underlying cause of this error is unknown. Please submit a bug report with the file attached."
) value : @ "" table : ( ( void * ) 0 ) ]
(@"The underlying cause of this error is unknown. Please submit a bug report with the file attached.", @"Error informative text");
1143
1144 nsError = [NSError mutableLocalErrorWithCode:kBDSKDocumentSaveError localizedDescription:errTitle underlyingError:nsError];
1145 [nsError setValue:errMsg forKey:NSLocalizedRecoverySuggestionErrorKey];
1146 }
1147 // needed because of finalize changes; don't send -clearChangeCount if the save failed for any reason, or if we're autosaving!
1148 else if (docState.currentSaveOperationType != NSAutosaveOperation && docState.currentSaveOperationType != NSSaveToOperation)
1149 [self performSelector:@selector(clearChangeCount) withObject:nil( ( void * ) 0 ) afterDelay:0.01];
1150
1151 // setting to nil is okay
1152 if (outError) *outError = nsError;
1153
1154 return success;
1155}
1156
1157- (BOOL)writeSafelyToURL:(NSURL *)absoluteURL ofType:(NSString *)typeName forSaveOperation:(NSSaveOperationType)saveOperation error:(NSError **)outError;
1158{
1159 BOOL didSave = [super writeSafelyToURL:absoluteURL ofType:typeName forSaveOperation:saveOperation error:outError];
1160
1161#if defined(MAC_OS_X_VERSION_10_5) && (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5)
1162
1163 /*
1164 This is a workaround for https://sourceforge.net/tracker/index.php?func=detail&aid=1867790&group_id=61487&atid=497423
1165 Filed as rdar://problem/5679370
1166
1167 I'm not sure what the semantics of this operation are for NSAutosaveOperation, so it's excluded (but uses a different code path anyway, at least on Leopard). This also doesn't get hit for save-as or save-to since they don't do a safe-save, but they're handled anyway. FSExchangeObjects apparently avoids the bugs in FSPathReplaceObject, but doesn't preserve all of the metadata that those do. It's a shame that Apple can't preserve the file content as well as they preserve the metadata; I'd rather lose the ACLs than lose my bibliography.
1168
1169 TODO: xattr handling, package vs. flat file (overwrite directory)?
1170 xattrs from BibDesk seem to be preserved, so I'm not going to bother with that.
1171
1172 TESTED: On AFP volume served by 10.4.11 Server, saving from 10.5.1 client; on AFP volume served by 10.5.1 client, saving from 10.5.1 client. Autosave, Save-As, and Save were tested. Saving to a local HFS+ volume doesn't hit this code path, and neither does saving to a FAT-32 thumb drive.
1173
1174 */
1175
1176 NSParameterAssertdo { if ( ! ( ( floor ( NSAppKitVersionNumber ) > 824 ) ) )
{ [ [ NSAssertionHandler currentHandler ] handleFailureInMethod
: _cmd object : self file : [ NSString stringWithUTF8String :
"/Volumes/Local/Users/amaxwell/build/bibdesk-clean/BibDocument.m"
] lineNumber : 1176 description : ( @ "Invalid parameter not satisfying: %s"
) , ( "floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_4"
) , ( 0 ) , ( 0 ) , ( 0 ) , ( 0 ) ] ; } } while ( 0 )
(floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_4);
1177
1178 if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_4824 && NO( BOOL ) 0 == didSave && [absoluteURL isFileURL] && NSAutosaveOperation != saveOperation) {
1179
1180 NSFileManager *fileManager = [NSFileManager defaultManager];
1181
1182 // this will create a new file on the same volume as the original file, which we will overwrite
1183 // FSExchangeObjects requires both files to be on the same volume
1184 NSString *tmpPath = [fileManager temporaryPathForWritingToPath:[absoluteURL path] allowOriginalDirectory:YES( BOOL ) 1 error:outError];
1185 NSURL *saveToURL = nil( ( void * ) 0 );
1186
1187 // at this point, we're guaranteed that absoluteURL is non-nil and is a fileURL, but the file may not exist
1188
1189 // save to or save as; file doesn't exist, so overwrite it
1190 if (NSSaveOperation != saveOperation)
1191 saveToURL = absoluteURL;
1192 else if (nil( ( void * ) 0 ) != tmpPath)
1193 saveToURL = [NSURL fileURLWithPath:tmpPath];
1194
1195 // if tmpPath failed, saveToURL is nil
1196 if (nil( ( void * ) 0 ) != saveToURL)
1197 didSave = [self writeToURL:saveToURL ofType:typeName forSaveOperation:saveOperation originalContentsURL:absoluteURL error:outError];
1198
1199 if (didSave) {
1200 NSMutableDictionary *fattrs = [NSMutableDictionary dictionary];
1201 [fattrs addEntriesFromDictionary:[self fileAttributesToWriteToURL:saveToURL ofType:typeName forSaveOperation:saveOperation originalContentsURL:absoluteURL error:outError]];
1202
1203 // copy POSIX permissions from the old file
1204 NSNumber *posixPerms = nil( ( void * ) 0 );
1205
1206 if ([fileManager fileExistsAtPath:[absoluteURL path]])
1207 posixPerms = [[fileManager fileAttributesAtPath:[absoluteURL path] traverseLink:YES( BOOL ) 1] objectForKey:NSFilePosixPermissions];
1208
1209 if (nil( ( void * ) 0 ) != posixPerms)
1210 [fattrs setObject:posixPerms forKey:NSFilePosixPermissions];
1211
1212 // not checking return value here; non-critical
1213 if ([fattrs count])
1214 [fileManager changeFileAttributes:fattrs atPath:[saveToURL path]];
1215 }
1216
1217 // If this is not an overwriting operation, we already saved to absoluteURL, and we're done
1218 // If this is an overwriting operation, do an atomic swap of the files
1219 if (didSave && NSSaveOperation == saveOperation) {
1220
1221 FSRef originalRef, newRef;
1222 OSStatus err = coreFoundationUnknownErr;
1223
1224 FSCatalogInfo catalogInfo;
1225 if (CFURLGetFSRef((CFURLRef)absoluteURL, &originalRef))
1226 err = noErr;
1227
1228 if (noErr == err)
1229 err = FSGetCatalogInfo(&originalRef, kFSCatInfoVolume, &catalogInfo, NULL( ( void * ) 0 ), NULL( ( void * ) 0 ), NULL( ( void * ) 0 ));
1230
1231 GetVolParmsInfoBuffer infoBuffer;
1232 err = FSGetVolumeParms(catalogInfo.volume, &infoBuffer, sizeof(GetVolParmsInfoBuffer));
1233
1234 if (noErr == err) {
1235
1236 // only meaningful in v3 or greater GetVolParmsInfoBuffer
1237 SInt32 vmExtAttr = infoBuffer.vMExtendedAttributes;
1238
1239 // in v2 or less or v3 without HFS+ support, the File Manager will implement FSExchangeObjects if bHasFileIDs is set
1240
1241 // MoreFilesX.h has macros that show how to read the bitfields for the enums
1242 if (infoBuffer.vMVersion > 2 && (vmExtAttr & (1L << bSupportsHFSPlusAPIs)) != 0 && (vmExtAttr & (1L << bSupportsFSExchangeObjects)) != 0)
1243 err = noErr;
1244 else if ((infoBuffer.vMVersion <= 2 || (vmExtAttr & (1L << bSupportsHFSPlusAPIs)) == 0) && (infoBuffer.vMAttrib & (1L << bHasFileIDs)) != 0)
1245 err = noErr;
1246 else
1247 err = errFSUnknownCall;
1248
1249 // do an atomic swap of the files
1250 // On an AFP volume (Server 10.4.11), xattrs from the original file are preserved using either function
1251
1252 if (noErr == err && CFURLGetFSRef((CFURLRef)saveToURL, &newRef)) {
1253 // this avoids breaking aliases and FSRefs
1254 err = FSExchangeObjects(&newRef, &originalRef);
1255 }
1256 else /* if we couldn't get an FSRef or bSupportsFSExchangeObjects is not supported */ {
1257 // rename() is atomic, but it probably breaks aliases and FSRefs
1258 // FSExchangeObjects() uses exchangedata() so there's no point in trying that
1259 err = rename([[saveToURL path] fileSystemRepresentation], [[absoluteURL path] fileSystemRepresentation]);
1260 }
1261 }
1262
1263 if (noErr != err) {
1264 didSave = NO( BOOL ) 0;
1265 if (outError) *outError = [NSError errorWithDomain:NSOSStatusErrorDomain code:err userInfo:nil( ( void * ) 0 )];
1266 }
1267 else if ([self keepBackupFile] == NO( BOOL ) 0) {
1268 // not checking return value here; non-critical, and fails if rename() was used
1269 [fileManager removeFileAtPath:[saveToURL path] handler:nil( ( void * ) 0 )];
1270 }
1271 }
1272 }
1273
1274#endif
1275
1276 return didSave;
1277}
1278
1279- (void)clearChangeCount{
1280 [self updateChangeCount:NSChangeCleared];
1281}
1282
1283- (BOOL)writeArchiveToURL:(NSURL *)fileURL forPublications:(NSArray *)items error:(NSError **)outError{
1284 if([items count]) NSParameterAssertdo { if ( ! ( ( [ [ items objectAtIndex : 0 ] isKindOfClass :
[ BibItem class ] ] ) ) ) { [ [ NSAssertionHandler currentHandler
] handleFailureInMethod : _cmd object : self file : [ NSString
stringWithUTF8String : "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/BibDocument.m"
] lineNumber : 1284 description : ( @ "Invalid parameter not satisfying: %s"
) , ( "[[items objectAtIndex:0] isKindOfClass:[BibItem class]]"
) , ( 0 ) , ( 0 ) , ( 0 ) , ( 0 ) ] ; } } while ( 0 )
([[items objectAtIndex:0] isKindOfClass:[BibItem class]]);
1285
1286 NSString *path = [[fileURL path] stringByDeletingPathExtension];
1287 NSFileManager *fm = [NSFileManager defaultManager];
1288 NSEnumerator *itemEnum = [items objectEnumerator];
1289 BibItem *item;
1290 NSString *filePath;
1291 NSString *commonParent = nil( ( void * ) 0 );
1292 BOOL success = YES( BOOL ) 1;
1293 NSMutableSet *localFiles = [NSMutableSet set];
1294
1295 if (success = [fm createDirectoryAtPath:path attributes:nil( ( void * ) 0 )]) {
1296 while (item = [itemEnum nextObject]) {
1297 NSEnumerator *fileEnum = [[item localFiles] objectEnumerator];
1298 BDSKLinkedFile *file;
1299 while (file = [fileEnum nextObject]) {
1300 if (filePath = [[file URL] path]) {
1301 [localFiles addObject:filePath];
1302 if (commonParent)
1303 commonParent = [NSString commonRootPathOfFilename:[filePath stringByDeletingLastPathComponent] andFilename:commonParent];
1304 else
1305 commonParent = [filePath stringByDeletingLastPathComponent];
1306 }
1307 }
1308 }
1309
1310 NSStringEncoding encoding = [saveTextEncodingPopupButton encoding] ? [saveTextEncodingPopupButton encoding] : [BDSKStringEncodingManager defaultEncoding];
1311 NSData *bibtexData = [self bibTeXDataForPublications:items encoding:encoding droppingInternal:NO( BOOL ) 0 relativeToPath:commonParent error:outError];
1312 NSString *bibtexPath = [[path stringByAppendingPathComponent:[path lastPathComponent]] stringByAppendingPathExtension:@"bib"];
1313
1314 success = [bibtexData writeToFile:bibtexPath options:0 error:outError];
1315 itemEnum = [localFiles objectEnumerator];
1316
1317 while (success && (filePath = [itemEnum nextObject])) {
1318 if ([fm fileExistsAtPath:filePath]) {
1319 NSString *relativePath = commonParent ? [commonParent relativePathToFilename:filePath] : [filePath lastPathComponent];
1320 NSString *targetPath = [path stringByAppendingPathComponent:relativePath];
1321
1322 if ([fm fileExistsAtPath:targetPath])
1323 targetPath = [fm uniqueFilePathWithName:[targetPath stringByDeletingLastPathComponent] atPath:[targetPath lastPathComponent]];
1324 success = [fm createPathToFile:targetPath attributes:nil( ( void * ) 0 ) error:NULL( ( void * ) 0 )];
1325 if (success)
1326 success = [fm copyPath:filePath toPath:targetPath handler:nil( ( void * ) 0 )];
1327 }
1328 }
1329
1330 if (success) {
1331 NSTask *task = [[[NSTask alloc] init] autorelease];
1332 [task setLaunchPath:@"/usr/bin/tar"];
1333 [task setArguments:[NSArray arrayWithObjects:@"czf", [[fileURL path] lastPathComponent], [path lastPathComponent], nil( ( void * ) 0 )]];
1334 [task setCurrentDirectoryPath:[path stringByDeletingLastPathComponent]];
1335 [task launch];
1336 if ([task isRunning])
1337 [task waitUntilExit];
1338 success = [task terminationStatus] == 0;
1339 [fm removeFileAtPath:path handler:nil( ( void * ) 0 )];
1340 }
1341 }
1342
1343 return success;
1344}
1345
1346#pragma mark Data representations
1347
1348- (NSFileWrapper *)fileWrapperOfType:(NSString *)aType error:(NSError **)outError
1349{
1350 return [self fileWrapperOfType:aType forPublications:publications error:outError];
1351}
1352
1353- (NSFileWrapper *)fileWrapperOfType:(NSString *)aType forPublications:(NSArray *)items error:(NSError **)outError
1354{
1355 NSFileWrapper *fileWrapper = nil( ( void * ) 0 );
1356
1357 // check if we need a fileWrapper; only needed for RTFD templates
1358 BDSKTemplate *selectedTemplate = [BDSKTemplate templateForStyle:aType];
1359 if([selectedTemplate templateFormat] & BDSKRTFDTemplateFormat){
1360 fileWrapper = [self fileWrapperForPublications:items usingTemplate:selectedTemplate];
1361 if(fileWrapper == nil( ( void * ) 0 )){
1362 if (outError)
1363 *outError = [NSError mutableLocalErrorWithCode:kBDSKDocumentSaveError localizedDescription:NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Unable to create file wrapper for the selected template"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"Unable to create file wrapper for the selected template", @"Error description")];
1364 }
1365 }else if ([aType isEqualToString:BDSKArchiveDocumentType] || [aType isEqualToUTI:[[NSWorkspace sharedWorkspace] UTIForPathExtension:@"tgz"]]){
1366 OBASSERT_NOT_REACHEDdo { OBAssertFailed ( "NOTREACHED" , "Should not save a fileWrapper for archive"
, "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/BibDocument.m"
, 1366 ) ; } while ( ( BOOL ) 0 )
("Should not save a fileWrapper for archive");
1367 }else{
1368 NSError *error = nil( ( void * ) 0 );
1369 NSData *data = [self dataOfType:aType forPublications:items error:&error];
1370 if(data != nil( ( void * ) 0 ) && error == nil( ( void * ) 0 )){
1371 fileWrapper = [[[NSFileWrapper alloc] initRegularFileWithContents:data] autorelease];
1372 } else {
1373 if(outError != NULL( ( void * ) 0 ))
1374 *outError = error;
1375 }
1376 }
1377 return fileWrapper;
1378}
1379
1380- (NSData *)dataOfType:(NSString *)aType error:(NSError **)outError
1381{
1382 return [self dataOfType:aType forPublications:publications error:outError];
1383}
1384
1385- (NSData *)dataOfType:(NSString *)aType forPublications:(NSArray *)items error:(NSError **)outError
1386{
1387 NSData *data = nil( ( void * ) 0 );
1388 NSError *error = nil( ( void * ) 0 );
1389 NSStringEncoding encoding = [self documentStringEncoding];
1390 NSParameterAssertdo { if ( ! ( ( encoding != 0 ) ) ) { [ [ NSAssertionHandler currentHandler
] handleFailureInMethod : _cmd object : self file : [ NSString
stringWithUTF8String : "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/BibDocument.m"
] lineNumber : 1390 description : ( @ "Invalid parameter not satisfying: %s"
) , ( "encoding != 0" ) , ( 0 ) , ( 0 ) , ( 0 ) , ( 0 ) ] ; }
} while ( 0 )
(encoding != 0);
1391
1392 BOOL isBibTeX = [aType isEqualToString:BDSKBibTeXDocumentType] || [aType isEqualToUTI:[[NSWorkspace sharedWorkspace] UTIForPathExtension:@"bib"]];
1393
1394 // export operations need their own encoding
1395 if(NSSaveToOperation == docState.currentSaveOperationType)
1396 encoding = [saveTextEncodingPopupButton encoding] ? [saveTextEncodingPopupButton encoding] : [BDSKStringEncodingManager defaultEncoding];
1397
1398 if (isBibTeX){
1399 if([[OFPreferenceWrapper sharedPreferenceWrapper] boolForKey:BDSKAutoSortForCrossrefsKey])
1400 [self performSortForCrossrefs];
1401 data = [self bibTeXDataForPublications:items encoding:encoding droppingInternal:NO( BOOL ) 0 relativeToPath:[[saveTargetURL path] stringByDeletingLastPathComponent] error:&error];
1402 }else if ([aType isEqualToString:BDSKRISDocumentType] || [aType isEqualToUTI:[[NSWorkspace sharedWorkspace] UTIForPathExtension:@"ris"]]){
1403 data = [self RISDataForPublications:items encoding:encoding error:&error];
1404 }else if ([aType isEqualToString:BDSKMinimalBibTeXDocumentType]){
1405 data = [self bibTeXDataForPublications:items encoding:encoding droppingInternal:YES( BOOL ) 1 relativeToPath:[[saveTargetURL path] stringByDeletingLastPathComponent] error:&error];
1406 }else if ([aType isEqualToString:BDSKLTBDocumentType] || [aType isEqualToUTI:[[NSWorkspace sharedWorkspace] UTIForPathExtension:@"ltb"]]){
1407 data = [self LTBDataForPublications:items encoding:encoding error:&error];
1408 }else if ([aType isEqualToString:BDSKEndNoteDocumentType]){
1409 data = [self endNoteDataForPublications:items];
1410 }else if ([aType isEqualToString:BDSKMODSDocumentType] || [aType isEqualToUTI:[[NSWorkspace sharedWorkspace] UTIForPathExtension:@"mods"]]){
1411 data = [self MODSDataForPublications:items];
1412 }else if ([aType isEqualToString:BDSKAtomDocumentType] || [aType isEqualToUTI:[[NSWorkspace sharedWorkspace] UTIForPathExtension:@"atom"]]){
1413 data = [self atomDataForPublications:items];
1414 }else{
1415 BDSKTemplate *selectedTemplate = [BDSKTemplate templateForStyle:aType];
1416 NSParameterAssertdo { if ( ! ( ( ( ( void * ) 0 ) != selectedTemplate ) ) ) { [
[ NSAssertionHandler currentHandler ] handleFailureInMethod :
_cmd object : self file : [ NSString stringWithUTF8String : "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/BibDocument.m"
] lineNumber : 1416 description : ( @ "Invalid parameter not satisfying: %s"
) , ( "nil != selectedTemplate" ) , ( 0 ) , ( 0 ) , ( 0 ) , (
0 ) ] ; } } while ( 0 )
(nil != selectedTemplate);
1417 BDSKTemplateFormat templateFormat = [selectedTemplate templateFormat];
1418
1419 if (templateFormat & BDSKRTFDTemplateFormat) {
1420 // @@ shouldn't reach here, should have already redirected to fileWrapperOfType:forPublications:error:
1421 } else if ([selectedTemplate scriptPath] != nil( ( void * ) 0 )) {
1422 data = [self dataForPublications:items usingTemplate:selectedTemplate];
1423 } else if (templateFormat & BDSKPlainTextTemplateFormat) {
1424 data = [self stringDataForPublications:items usingTemplate:selectedTemplate];
1425 } else {
1426 data = [self attributedStringDataForPublications:items usingTemplate:selectedTemplate];
1427 }
1428 }
1429
1430 // grab the underlying error; if we recognize it, pass it up as a kBDSKDocumentSaveError
1431 if(nil( ( void * ) 0 ) == data && outError){
1432 // see if this was an encoding failure; if so, we can suggest how to fix it
1433 // NSLocalizedRecoverySuggestion is appropriate for display as error message in alert
1434 if(kBDSKStringEncodingError == [error code]){
1435 // encoding conversion failure (string to data)
1436 NSStringEncoding usedEncoding = [[error valueForKey:NSStringEncodingErrorKey] intValue];
1437 NSMutableString *message = [NSMutableString stringWithFormat:NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "The document cannot be saved using %@ encoding."
) value : @ "" table : ( ( void * ) 0 ) ]
(@"The document cannot be saved using %@ encoding.", @"Error informative text"), [NSString localizedNameOfStringEncoding:usedEncoding]];
1438
1439 // this is likely nil, so keep NSMutableString from raising
1440 if ([error valueForKey:NSLocalizedRecoverySuggestionErrorKey]) {
1441 [message appendString:@" "];
1442 [message appendString:[error valueForKey:NSLocalizedRecoverySuggestionErrorKey]];
1443 }
1444 [message appendString:@" "];
1445
1446 // see if TeX conversion is enabled; it will help for ASCII, and possibly other encodings, but not UTF-8
1447 // only for BibTeX, though!
1448 if (isBibTeX && [[OFPreferenceWrapper sharedPreferenceWrapper] boolForKey:BDSKShouldTeXifyWhenSavingAndCopyingKey] == NO( BOOL ) 0) {
1449 [message appendFormat:NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "You should enable accented character conversion in the Files preference pane or save using an encoding such as %@."
) value : @ "" table : ( ( void * ) 0 ) ]
(@"You should enable accented character conversion in the Files preference pane or save using an encoding such as %@.", @"Error informative text"), [NSString localizedNameOfStringEncoding:NSUTF8StringEncoding]];
1450 } else if (NSUTF8StringEncoding != usedEncoding){
1451 // could suggest disabling TeX conversion, but the error might be from something out of the range of what we try to convert, so combining TeXify && UTF-8 would work
1452 [message appendFormat:NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "You should save using an encoding such as %@."
) value : @ "" table : ( ( void * ) 0 ) ]
(@"You should save using an encoding such as %@.", @"Error informative text"), [NSString localizedNameOfStringEncoding:NSUTF8StringEncoding]];
1453 } else {
1454 // if UTF-8 fails, you're hosed...
1455 [message appendString:NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Please report this error to BibDesk's developers."
) value : @ "" table : ( ( void * ) 0 ) ]
(@"Please report this error to BibDesk's developers.", @"Error informative text")];
1456 }
1457
1458 error = [NSError mutableLocalErrorWithCode:kBDSKDocumentSaveError localizedDescription:NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Unable to save document"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"Unable to save document", @"Error description") underlyingError:error];
1459 [error setValue:message forKey:NSLocalizedRecoverySuggestionErrorKey];
1460
1461 }
1462 *outError = error;
1463 }
1464
1465 return data;
1466}
1467
1468- (NSData *)atomDataForPublications:(NSArray *)items{
1469 NSEnumerator *e = [items objectEnumerator];
1470 BibItem *pub = nil( ( void * ) 0 );
1471 NSMutableData *d = [NSMutableData data];
1472
1473 [d appendUTF8DataFromString:@"<?xml version=\"1.0\" encoding=\"UTF-8\"?><feed xmlns=\"http://purl.org/atom/ns#\">"];
1474
1475 if([items count]) NSParameterAssertdo { if ( ! ( ( [ [ items objectAtIndex : 0 ] isKindOfClass :
[ BibItem class ] ] ) ) ) { [ [ NSAssertionHandler currentHandler
] handleFailureInMethod : _cmd object : self file : [ NSString
stringWithUTF8String : "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/BibDocument.m"
] lineNumber : 1475 description : ( @ "Invalid parameter not satisfying: %s"
) , ( "[[items objectAtIndex:0] isKindOfClass:[BibItem class]]"
) , ( 0 ) , ( 0 ) , ( 0 ) , ( 0 ) ] ; } } while ( 0 )
([[items objectAtIndex:0] isKindOfClass:[BibItem class]]);
1476
1477 // TODO: output general feed info
1478
1479 while(pub = [e nextObject]){
1480 [d appendUTF8DataFromString:@"<entry><title>foo</title><description>foo-2</description>"];
1481 [d appendUTF8DataFromString:@"<content type=\"application/xml+mods\">"];
1482 [d appendUTF8DataFromString:[pub MODSString]];
1483 [d appendUTF8DataFromString:@"</content>"];
1484 [d appendUTF8DataFromString:@"</entry>\n"];
1485 }
1486 [d appendUTF8DataFromString:@"</feed>"];
1487
1488 return d;
1489}
1490
1491- (NSData *)MODSDataForPublications:(NSArray *)items{
1492 NSEnumerator *e = [items objectEnumerator];
1493 BibItem *pub = nil( ( void * ) 0 );
1494 NSMutableData *d = [NSMutableData data];
1495
1496 if([items count]) NSParameterAssertdo { if ( ! ( ( [ [ items objectAtIndex : 0 ] isKindOfClass :
[ BibItem class ] ] ) ) ) { [ [ NSAssertionHandler currentHandler
] handleFailureInMethod : _cmd object : self file : [ NSString
stringWithUTF8String : "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/BibDocument.m"
] lineNumber : 1496 description : ( @ "Invalid parameter not satisfying: %s"
) , ( "[[items objectAtIndex:0] isKindOfClass:[BibItem class]]"
) , ( 0 ) , ( 0 ) , ( 0 ) , ( 0 ) ] ; } } while ( 0 )
([[items objectAtIndex:0] isKindOfClass:[BibItem class]]);
1497
1498 [d appendUTF8DataFromString:@"<?xml version=\"1.0\" encoding=\"UTF-8\"?><modsCollection xmlns=\"http://www.loc.gov/mods/v3\">"];
1499 while(pub = [e nextObject]){
1500 [d appendUTF8DataFromString:[pub MODSString]];
1501 [d appendUTF8DataFromString:@"\n"];
1502 }
1503 [d appendUTF8DataFromString:@"</modsCollection>"];
1504
1505 return d;
1506}
1507
1508- (NSData *)endNoteDataForPublications:(NSArray *)items{
1509 NSMutableData *d = [NSMutableData data];
1510
1511 if([items count]) NSParameterAssertdo { if ( ! ( ( [ [ items objectAtIndex : 0 ] isKindOfClass :
[ BibItem class ] ] ) ) ) { [ [ NSAssertionHandler currentHandler
] handleFailureInMethod : _cmd object : self file : [ NSString
stringWithUTF8String : "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/BibDocument.m"
] lineNumber : 1511 description : ( @ "Invalid parameter not satisfying: %s"
) , ( "[[items objectAtIndex:0] isKindOfClass:[BibItem class]]"
) , ( 0 ) , ( 0 ) , ( 0 ) , ( 0 ) ] ; } } while ( 0 )
([[items objectAtIndex:0] isKindOfClass:[BibItem class]]);
1512
1513 [d appendUTF8DataFromString:@"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<xml>\n<records>\n"];
1514 [d performSelector:@selector(appendUTF8DataFromString:) withObjectsByMakingObjectsFromArray:items performSelector:@selector(endNoteString)];
1515 [d appendUTF8DataFromString:@"</records>\n</xml>\n"];
1516
1517 return d;
1518}
1519
1520- (NSData *)bibTeXDataForPublications:(NSArray *)items encoding:(NSStringEncoding)encoding droppingInternal:(BOOL)drop relativeToPath:(NSString *)basePath error:(NSError **)outError{
1521 NSParameterAssertdo { if ( ! ( ( encoding != 0 ) ) ) { [ [ NSAssertionHandler currentHandler
] handleFailureInMethod : _cmd object : self file : [ NSString
stringWithUTF8String : "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/BibDocument.m"
] lineNumber : 1521 description : ( @ "Invalid parameter not satisfying: %s"
) , ( "encoding != 0" ) , ( 0 ) , ( 0 ) , ( 0 ) , ( 0 ) ] ; }
} while ( 0 )
(encoding != 0);
1522
1523 NSEnumerator *e = [items objectEnumerator];
1524 BibItem *pub = nil( ( void * ) 0 );
1525 NSMutableData *outputData = [NSMutableData dataWithCapacity:4096];
1526 NSData *pubData;
1527 NSError *error = nil( ( void * ) 0 );
1528 BOOL isOK = YES( BOOL ) 1;
1529
1530 BOOL shouldAppendFrontMatter = YES( BOOL ) 1;
1531 NSString *encodingName = [NSString localizedNameOfStringEncoding:encoding];
1532 NSStringEncoding groupsEncoding = [[BDSKStringEncodingManager sharedEncodingManager] isUnparseableEncoding:encoding] ? encoding : NSUTF8StringEncoding;
1533
1534 int options = 0;
1535 if ([[OFPreferenceWrapper sharedPreferenceWrapper] boolForKey:BDSKShouldTeXifyWhenSavingAndCopyingKey])
1536 options |= BDSKBibTeXOptionTeXifyMask;
1537 if (drop)
1538 options |= BDSKBibTeXOptionDropInternalMask;
1539
1540 if([[OFPreferenceWrapper sharedPreferenceWrapper] boolForKey:BDSKShouldUseTemplateFile]){
1541 NSMutableString *templateFile = [NSMutableString stringWithContentsOfFile:[[[OFPreferenceWrapper sharedPreferenceWrapper] stringForKey:BDSKOutputTemplateFileKey] stringByExpandingTildeInPath] usedEncoding:NULL( ( void * ) 0 ) error:NULL( ( void * ) 0 )];
1542
1543 if (templateFile == nil( ( void * ) 0 ))
1544 templateFile = [NSMutableString string];
1545
1546 NSString *userName = NSFullUserName();
1547 if ([userName canBeConvertedToEncoding:encoding] == NO( BOOL ) 0)
1548 userName = [[[NSString alloc] initWithData:[userName dataUsingEncoding:encoding allowLossyConversion:YES( BOOL ) 1] encoding:encoding] autorelease];
1549
1550 [templateFile appendFormat:@"\n%%%% Created for %@ at %@ \n\n", userName, [NSCalendarDate calendarDate]];
1551
1552 [templateFile appendFormat:@"\n%%%% Saved with string encoding %@ \n\n", encodingName];
1553
1554 // remove all whitespace so we can make a comparison; just collapsing isn't quite good enough, unfortunately
1555 NSString *collapsedTemplate = [templateFile stringByRemovingWhitespace];
1556 NSString *collapsedFrontMatter = [frontMatter stringByRemovingWhitespace];
1557 if([NSString isEmptyString:collapsedFrontMatter]){
1558 shouldAppendFrontMatter = NO( BOOL ) 0;
1559 }else if([collapsedTemplate containsString:collapsedFrontMatter]){
1560 NSLog(@"*** WARNING! *** Found duplicate preamble %@. Using template from preferences.", frontMatter);
1561 shouldAppendFrontMatter = NO( BOOL ) 0;
1562 }
1563
1564 isOK = [outputData appendDataFromString:templateFile encoding:encoding error:&error];
1565 if(NO( BOOL ) 0 == isOK)
1566 [error setValue:NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Unable to convert template string."
) value : @ "" table : ( ( void * ) 0 ) ]
(@"Unable to convert template string.", @"string encoding error context") forKey:NSLocalizedRecoverySuggestionErrorKey];
1567 }
1568
1569 NSData *doubleNewlineData = [@"\n\n" dataUsingEncoding:encoding];
1570
1571 // only append this if it wasn't redundant (this assumes that the original frontmatter is either a subset of the necessary frontmatter, or that the user's preferences should override in case of a conflict)
1572 if(isOK && shouldAppendFrontMatter){
1573 isOK = [outputData appendDataFromString:frontMatter encoding:encoding error:&error];
1574 if(NO( BOOL ) 0 == isOK)
1575 [error setValue:NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Unable to convert file header."
) value : @ "" table : ( ( void * ) 0 ) ]
(@"Unable to convert file header.", @"string encoding error context") forKey:NSLocalizedRecoverySuggestionErrorKey];
1576 [outputData appendData:doubleNewlineData];
1577 }
1578
1579 if(isOK && [documentInfo count]){
1580 isOK = [outputData appendDataFromString:[self documentInfoString] encoding:encoding error:&error];
1581 if(NO( BOOL ) 0 == isOK)
1582 [error setValue:NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Unable to convert document info."
) value : @ "" table : ( ( void * ) 0 ) ]
(@"Unable to convert document info.", @"string encoding error context") forKey:NSLocalizedRecoverySuggestionErrorKey];
1583 }
1584
1585 // output the document's macros:
1586 if(isOK){
1587 isOK = [outputData appendDataFromString:[[self macroResolver] bibTeXString] encoding:encoding error:&error];
1588 if(NO( BOOL ) 0 == isOK)
1589 [error setValue:NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Unable to convert macros."
) value : @ "" table : ( ( void * ) 0 ) ]
(@"Unable to convert macros.", @"string encoding error context") forKey:NSLocalizedRecoverySuggestionErrorKey];
1590 }
1591
1592 // output the bibs
1593
1594 if([items count]) NSParameterAssertdo { if ( ! ( ( [ [ items objectAtIndex : 0 ] isKindOfClass :
[ BibItem class ] ] ) ) ) { [ [ NSAssertionHandler currentHandler
] handleFailureInMethod : _cmd object : self file : [ NSString
stringWithUTF8String : "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/BibDocument.m"
] lineNumber : 1594 description : ( @ "Invalid parameter not satisfying: %s"
) , ( "[[items objectAtIndex:0] isKindOfClass:[BibItem class]]"
) , ( 0 ) , ( 0 ) , ( 0 ) , ( 0 ) ] ; } } while ( 0 )
([[items objectAtIndex:0] isKindOfClass:[BibItem class]]);
1595
1596 while(isOK && (pub = [e nextObject])){
1597 pubData = [pub bibTeXDataWithOptions:options relativeToPath:basePath encoding:encoding error:&error];
1598 if(isOK = pubData != nil( ( void * ) 0 )){
1599 [outputData appendData:doubleNewlineData];
1600 [outputData appendData:pubData];
1601 }else if([error valueForKey:NSLocalizedRecoverySuggestionErrorKey] == nil( ( void * ) 0 ))
1602 [error setValue:[NSString stringWithFormat:NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Unable to convert item with cite key %@."
) value : @ "" table : ( ( void * ) 0 ) ]
(@"Unable to convert item with cite key %@.", @"string encoding error context"), [pub citeKey]] forKey:NSLocalizedRecoverySuggestionErrorKey];
1603 }
1604
1605 if (drop == NO( BOOL ) 0) {
1606 // The data from groups is always UTF-8, and we shouldn't convert it unless we have an unparseable encoding; the comment key strings should be representable in any encoding
1607 if(isOK && ([[groups staticGroups] count] > 0)){
1608 isOK = [outputData appendDataFromString:@"\n\n@comment{BibDesk Static Groups{\n" encoding:encoding error:&error] &&
1609 [outputData appendStringData:[groups serializedGroupsDataOfType:BDSKStaticGroupType] convertedFromUTF8ToEncoding:groupsEncoding error:&error] &&
1610 [outputData appendDataFromString:@"}}" encoding:encoding error:&error];
1611 if(NO( BOOL ) 0 == isOK)
1612 [error setValue:NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Unable to convert static groups."
) value : @ "" table : ( ( void * ) 0 ) ]
(@"Unable to convert static groups.", @"string encoding error context") forKey:NSLocalizedRecoverySuggestionErrorKey];
1613 }
1614 if(isOK && ([[groups smartGroups] count] > 0)){
1615 isOK = [outputData appendDataFromString:@"\n\n@comment{BibDesk Smart Groups{\n" encoding:encoding error:&error] &&
1616 [outputData appendStringData:[groups serializedGroupsDataOfType:BDSKSmartGroupType] convertedFromUTF8ToEncoding:groupsEncoding error:&error] &&
1617 [outputData appendDataFromString:@"}}" encoding:encoding error:&error];
1618 [error setValue:NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Unable to convert smart groups."
) value : @ "" table : ( ( void * ) 0 ) ]
(@"Unable to convert smart groups.", @"string encoding error context") forKey:NSLocalizedRecoverySuggestionErrorKey];
1619 }
1620 if(isOK && ([[groups URLGroups] count] > 0)){
1621 isOK = [outputData appendDataFromString:@"\n\n@comment{BibDesk URL Groups{\n" encoding:encoding error:&error] &&
1622 [outputData appendStringData:[groups serializedGroupsDataOfType:BDSKURLGroupType] convertedFromUTF8ToEncoding:groupsEncoding error:&error] &&
1623 [outputData appendDataFromString:@"}}" encoding:encoding error:&error];
1624 if(NO( BOOL ) 0 == isOK)
1625 [error setValue:NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Unable to convert external file groups."
) value : @ "" table : ( ( void * ) 0 ) ]
(@"Unable to convert external file groups.", @"string encoding error context") forKey:NSLocalizedRecoverySuggestionErrorKey];
1626 }
1627 if(isOK && ([[groups scriptGroups] count] > 0)){
1628 isOK = [outputData appendDataFromString:@"\n\n@comment{BibDesk Script Groups{\n" encoding:encoding error:&error] &&
1629 [outputData appendStringData:[groups serializedGroupsDataOfType:BDSKScriptGroupType] convertedFromUTF8ToEncoding:groupsEncoding error:&error] &&
1630 [outputData appendDataFromString:@"}}" encoding:encoding error:&error];
1631 if(NO( BOOL ) 0 == isOK)
1632 [error setValue:NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Unable to convert script groups."
) value : @ "" table : ( ( void * ) 0 ) ]
(@"Unable to convert script groups.", @"string encoding error context") forKey:NSLocalizedRecoverySuggestionErrorKey];
1633 }
1634 }
1635
1636 if(isOK)
1637 [outputData appendDataFromString:@"\n" encoding:encoding error:&error];
1638
1639 if (NO( BOOL ) 0 == isOK && outError != NULL( ( void * ) 0 )) *outError = error;
1640
1641 return isOK ? outputData : nil( ( void * ) 0 );
1642
1643}
1644
1645- (NSData *)RISDataForPublications:(NSArray *)items encoding:(NSStringEncoding)encoding error:(NSError **)error{
1646
1647 NSParameterAssertdo { if ( ! ( ( encoding ) ) ) { [ [ NSAssertionHandler currentHandler
] handleFailureInMethod : _cmd object : self file : [ NSString
stringWithUTF8String : "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/BibDocument.m"
] lineNumber : 1647 description : ( @ "Invalid parameter not satisfying: %s"
) , ( "encoding" ) , ( 0 ) , ( 0 ) , ( 0 ) , ( 0 ) ] ; } } while
( 0 )
(encoding);
1648
1649 if([items count]) NSParameterAssertdo { if ( ! ( ( [ [ items objectAtIndex : 0 ] isKindOfClass :
[ BibItem class ] ] ) ) ) { [ [ NSAssertionHandler currentHandler
] handleFailureInMethod : _cmd object : self file : [ NSString
stringWithUTF8String : "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/BibDocument.m"
] lineNumber : 1649 description : ( @ "Invalid parameter not satisfying: %s"
) , ( "[[items objectAtIndex:0] isKindOfClass:[BibItem class]]"
) , ( 0 ) , ( 0 ) , ( 0 ) , ( 0 ) ] ; } } while ( 0 )
([[items objectAtIndex:0] isKindOfClass:[BibItem class]]);
1650 NSString *RISString = [self RISStringForPublications:items];
1651 NSData *data = [RISString dataUsingEncoding:encoding allowLossyConversion:NO( BOOL ) 0];
1652 if (nil( ( void * ) 0 ) == data && error) {
1653 OFErrorWithInfo_OFError ( error , @ "edu.ucsd.cs.mmccrack.bibdesk" , kBDSKStringEncodingError
, "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/BibDocument.m"
, 1653 , NSLocalizedDescriptionKey , [ NSString stringWithFormat
: [ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Unable to convert the bibliography to encoding %@"
) value : @ "" table : ( ( void * ) 0 ) ] , [ NSString localizedNameOfStringEncoding
: encoding ] ] , NSStringEncodingErrorKey , [ NSNumber numberWithInt
: encoding ] , ( ( void * ) 0 ) )
(error, kBDSKStringEncodingError, NSLocalizedDescriptionKey, [NSString stringWithFormat:NSLocalizedString(@"Unable to convert the bibliography to encoding %@", @"Error description"), [NSString localizedNameOfStringEncoding:encoding]], NSStringEncodingErrorKey, [NSNumber numberWithInt:encoding], nil);
1654 }
1655 return data;
1656}
1657
1658- (NSData *)LTBDataForPublications:(NSArray *)items encoding:(NSStringEncoding)encoding error:(NSError **)error{
1659
1660 NSParameterAssertdo { if ( ! ( ( encoding ) ) ) { [ [ NSAssertionHandler currentHandler
] handleFailureInMethod : _cmd object : self file : [ NSString
stringWithUTF8String : "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/BibDocument.m"
] lineNumber : 1660 description : ( @ "Invalid parameter not satisfying: %s"
) , ( "encoding" ) , ( 0 ) , ( 0 ) , ( 0 ) , ( 0 ) ] ; } } while
( 0 )
(encoding);
1661
1662 if([items count]) NSParameterAssertdo { if ( ! ( ( [ [ items objectAtIndex : 0 ] isKindOfClass :
[ BibItem class ] ] ) ) ) { [ [ NSAssertionHandler currentHandler
] handleFailureInMethod : _cmd object : self file : [ NSString
stringWithUTF8String : "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/BibDocument.m"
] lineNumber : 1662 description : ( @ "Invalid parameter not satisfying: %s"
) , ( "[[items objectAtIndex:0] isKindOfClass:[BibItem class]]"
) , ( 0 ) , ( 0 ) , ( 0 ) , ( 0 ) ] ; } } while ( 0 )
([[items objectAtIndex:0] isKindOfClass:[BibItem class]]);
1663
1664 NSPasteboard *pboard = [NSPasteboard pasteboardWithUniqueName];
1665 [pboardHelper declareType:NSStringPboardType dragCopyType:BDSKLTBDragCopyType forItems:items forPasteboard:pboard];
1666 NSString *ltbString = [pboard stringForType:NSStringPboardType];
1667 [pboardHelper clearPromisedTypesForPasteboard:pboard];
1668 if(ltbString == nil( ( void * ) 0 )){
1669 if (error) OFErrorWithInfo_OFError ( error , @ "edu.ucsd.cs.mmccrack.bibdesk" , kBDSKDocumentSaveError
, "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/BibDocument.m"
, 1669 , NSLocalizedDescriptionKey , [ [ NSBundle mainBundle
] localizedStringForKey : ( @ "Unable to run TeX processes for these publications"
) value : @ "" table : ( ( void * ) 0 ) ] , ( ( void * ) 0 )
)
(error, kBDSKDocumentSaveError, NSLocalizedDescriptionKey, NSLocalizedString(@"Unable to run TeX processes for these publications", @"Error description"), nil);
1670 return nil( ( void * ) 0 );
1671 }
1672
1673 NSMutableString *s = [NSMutableString stringWithString:@"\\documentclass{article}\n\\usepackage{amsrefs}\n\\begin{document}\n\n"];
1674 [s appendString:ltbString];
1675 [s appendString:@"\n\\end{document}\n"];
1676
1677 NSData *data = [s dataUsingEncoding:encoding allowLossyConversion:NO( BOOL ) 0];
1678 if (nil( ( void * ) 0 ) == data && error) {
1679 OFErrorWithInfo_OFError ( error , @ "edu.ucsd.cs.mmccrack.bibdesk" , kBDSKStringEncodingError
, "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/BibDocument.m"
, 1679 , NSLocalizedDescriptionKey , [ NSString stringWithFormat
: [ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Unable to convert the bibliography to encoding %@"
) value : @ "" table : ( ( void * ) 0 ) ] , [ NSString localizedNameOfStringEncoding
: encoding ] ] , NSStringEncodingErrorKey , [ NSNumber numberWithInt
: encoding ] , ( ( void * ) 0 ) )
(error, kBDSKStringEncodingError, NSLocalizedDescriptionKey, [NSString stringWithFormat:NSLocalizedString(@"Unable to convert the bibliography to encoding %@", @"Error description"), [NSString localizedNameOfStringEncoding:encoding]], NSStringEncodingErrorKey, [NSNumber numberWithInt:encoding], nil);
1680 }
1681 return data;
1682}
1683
1684- (NSData *)stringDataForPublications:(NSArray *)items usingTemplate:(BDSKTemplate *)template{
1685 if([items count]) NSParameterAssertdo { if ( ! ( ( [ [ items objectAtIndex : 0 ] isKindOfClass :
[ BibItem class ] ] ) ) ) { [ [ NSAssertionHandler currentHandler
] handleFailureInMethod : _cmd object : self file : [ NSString
stringWithUTF8String : "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/BibDocument.m"
] lineNumber : 1685 description : ( @ "Invalid parameter not satisfying: %s"
) , ( "[[items objectAtIndex:0] isKindOfClass:[BibItem class]]"
) , ( 0 ) , ( 0 ) , ( 0 ) , ( 0 ) ] ; } } while ( 0 )
([[items objectAtIndex:0] isKindOfClass:[BibItem class]]);
1686
1687 OBPRECONDITIONdo { if ( ! ( ( ( void * ) 0 ) != template && ( [ template
templateFormat ] & BDSKPlainTextTemplateFormat ) ) ) OBAssertFailed
( "PRECONDITION" , "nil != template && ([template templateFormat] & BDSKPlainTextTemplateFormat)"
, "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/BibDocument.m"
, 1687 ) ; } while ( ( BOOL ) 0 )
(nil != template && ([template templateFormat] & BDSKPlainTextTemplateFormat));
1688
1689 NSString *fileTemplate = [BDSKTemplateObjectProxy stringByParsingTemplate:template withObject:self publications:items];
1690 return [fileTemplate dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:NO( BOOL ) 0];
1691}
1692
1693- (NSData *)attributedStringDataForPublications:(NSArray *)items usingTemplate:(BDSKTemplate *)template{
1694 if([items count]) NSParameterAssertdo { if ( ! ( ( [ [ items objectAtIndex : 0 ] isKindOfClass :
[ BibItem class ] ] ) ) ) { [ [ NSAssertionHandler currentHandler
] handleFailureInMethod : _cmd object : self file : [ NSString
stringWithUTF8String : "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/BibDocument.m"
] lineNumber : 1694 description : ( @ "Invalid parameter not satisfying: %s"
) , ( "[[items objectAtIndex:0] isKindOfClass:[BibItem class]]"
) , ( 0 ) , ( 0 ) , ( 0 ) , ( 0 ) ] ; } } while ( 0 )
([[items objectAtIndex:0] isKindOfClass:[BibItem class]]);
1695
1696 OBPRECONDITIONdo { if ( ! ( ( ( void * ) 0 ) != template ) ) OBAssertFailed
( "PRECONDITION" , "nil != template" , "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/BibDocument.m"
, 1696 ) ; } while ( ( BOOL ) 0 )
(nil != template);
1697 BDSKTemplateFormat format = [template templateFormat];
1698 OBPRECONDITIONdo { if ( ! ( format & ( BDSKRTFTemplateFormat | BDSKDocTemplateFormat
| BDSKOdtTemplateFormat | BDSKRichHTMLTemplateFormat ) ) ) OBAssertFailed
( "PRECONDITION" , "format & (BDSKRTFTemplateFormat | BDSKDocTemplateFormat | BDSKOdtTemplateFormat | BDSKRichHTMLTemplateFormat)"
, "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/BibDocument.m"
, 1698 ) ; } while ( ( BOOL ) 0 )
(format & (BDSKRTFTemplateFormat | BDSKDocTemplateFormat | BDSKOdtTemplateFormat | BDSKRichHTMLTemplateFormat));
1699 NSDictionary *docAttributes = nil( ( void * ) 0 );
1700 NSAttributedString *fileTemplate = [BDSKTemplateObjectProxy attributedStringByParsingTemplate:template withObject:self publications:items documentAttributes:&docAttributes];
1701 NSMutableDictionary *mutableAttributes = [NSMutableDictionary dictionaryWithDictionary:docAttributes];
1702
1703 // create some useful metadata, with an option to disable for the paranoid
1704 if([[NSUserDefaults standardUserDefaults] boolForKey:@"BDSKDisableExportAttributes"]){
1705 [mutableAttributes addEntriesFromDictionary:[NSDictionary dictionaryWithObjectsAndKeys:NSFullUserName(), NSAuthorDocumentAttribute, [NSDate date], NSCreationTimeDocumentAttribute, [NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "BibDesk export of "
) value : @ "" table : ( ( void * ) 0 ) ]
(@"BibDesk export of ", @"Error description") stringByAppendingString:[[self fileURL] lastPathComponent]], NSTitleDocumentAttribute, nil( ( void * ) 0 )]];
1706 }
1707
1708 if (format & BDSKRTFTemplateFormat) {
1709 return [fileTemplate RTFFromRange:NSMakeRange(0,[fileTemplate length]) documentAttributes:mutableAttributes];
1710 } else if (format & BDSKRichHTMLTemplateFormat) {
1711 [mutableAttributes setObject:NSHTMLTextDocumentType forKey:NSDocumentTypeDocumentAttribute];
1712 NSError *error = nil( ( void * ) 0 );
1713 return [fileTemplate dataFromRange:NSMakeRange(0,[fileTemplate length]) documentAttributes:mutableAttributes error:&error];
1714 } else if (format & BDSKDocTemplateFormat) {
1715 return [fileTemplate docFormatFromRange:NSMakeRange(0,[fileTemplate length]) documentAttributes:mutableAttributes];
1716 } else if ((format & BDSKOdtTemplateFormat) && floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_4824) {
1717 [mutableAttributes setObject:@"NSOpenDocument" forKey:NSDocumentTypeDocumentAttribute];
1718 NSError *error = nil( ( void * ) 0 );
1719 return [fileTemplate dataFromRange:NSMakeRange(0,[fileTemplate length]) documentAttributes:mutableAttributes error:&error];
1720 } else return nil( ( void * ) 0 );
1721}
1722
1723- (NSData *)dataForPublications:(NSArray *)items usingTemplate:(BDSKTemplate *)template{
1724 if([items count]) NSParameterAssertdo { if ( ! ( ( [ [ items objectAtIndex : 0 ] isKindOfClass :
[ BibItem class ] ] ) ) ) { [ [ NSAssertionHandler currentHandler
] handleFailureInMethod : _cmd object : self file : [ NSString
stringWithUTF8String : "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/BibDocument.m"
] lineNumber : 1724 description : ( @ "Invalid parameter not satisfying: %s"
) , ( "[[items objectAtIndex:0] isKindOfClass:[BibItem class]]"
) , ( 0 ) , ( 0 ) , ( 0 ) , ( 0 ) ] ; } } while ( 0 )
([[items objectAtIndex:0] isKindOfClass:[BibItem class]]);
1725
1726 OBPRECONDITIONdo { if ( ! ( ( ( void * ) 0 ) != template && ( ( void
* ) 0 ) != [ template scriptPath ] ) ) OBAssertFailed ( "PRECONDITION"
, "nil != template && nil != [template scriptPath]" ,
"/Volumes/Local/Users/amaxwell/build/bibdesk-clean/BibDocument.m"
, 1726 ) ; } while ( ( BOOL ) 0 )
(nil != template && nil != [template scriptPath]);
1727
1728 NSData *fileTemplate = [BDSKTemplateObjectProxy dataByParsingTemplate:template withObject:self publications:items];
1729 return fileTemplate;
1730}
1731
1732- (NSFileWrapper *)fileWrapperForPublications:(NSArray *)items usingTemplate:(BDSKTemplate *)template{
1733 if([items count]) NSParameterAssertdo { if ( ! ( ( [ [ items objectAtIndex : 0 ] isKindOfClass :
[ BibItem class ] ] ) ) ) { [ [ NSAssertionHandler currentHandler
] handleFailureInMethod : _cmd object : self file : [ NSString
stringWithUTF8String : "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/BibDocument.m"
] lineNumber : 1733 description : ( @ "Invalid parameter not satisfying: %s"
) , ( "[[items objectAtIndex:0] isKindOfClass:[BibItem class]]"
) , ( 0 ) , ( 0 ) , ( 0 ) , ( 0 ) ] ; } } while ( 0 )
([[items objectAtIndex:0] isKindOfClass:[BibItem class]]);
1734
1735 OBPRECONDITIONdo { if ( ! ( ( ( void * ) 0 ) != template && [ template
templateFormat ] & BDSKRTFDTemplateFormat ) ) OBAssertFailed
( "PRECONDITION" , "nil != template && [template templateFormat] & BDSKRTFDTemplateFormat"
, "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/BibDocument.m"
, 1735 ) ; } while ( ( BOOL ) 0 )
(nil != template && [template templateFormat] & BDSKRTFDTemplateFormat);
1736 NSDictionary *docAttributes = nil( ( void * ) 0 );
1737 NSAttributedString *fileTemplate = [BDSKTemplateObjectProxy attributedStringByParsingTemplate:template withObject:self publications:items documentAttributes:&docAttributes];
1738
1739 return [fileTemplate RTFDFileWrapperFromRange:NSMakeRange(0,[fileTemplate length]) documentAttributes:docAttributes];
1740}
1741
1742#pragma mark -
1743#pragma mark Opening and Loading Files
1744
1745- (BOOL)revertToContentsOfURL:(NSURL *)absoluteURL ofType:(NSString *)aType error:(NSError **)outError
1746{
1747 // first remove all editor windows, as they will be invalid afterwards
1748 unsigned int idx = [[self windowControllers] count];
1749 while(--idx)
1750 [[[self windowControllers] objectAtIndex:idx] close];
1751
1752 if([super revertToContentsOfURL:absoluteURL ofType:aType error:outError]){
1753 [self setSearchString:@""];
1754 [self updateSmartGroupsCountAndContent:NO( BOOL ) 0];
1755 [self updateCategoryGroupsPreservingSelection:YES( BOOL ) 1];
1756 [self sortGroupsByKey:sortGroupsKey]; // resort
1757 [tableView deselectAll:self]; // clear before resorting
1758 [self search:searchField]; // redo the search
1759 [self sortPubsByKey:nil( ( void * ) 0 )]; // resort
1760 return YES( BOOL ) 1;
1761 }
1762 return NO( BOOL ) 0;
1763}
1764
1765- (BOOL)readFromURL:(NSURL *)absoluteURL ofType:(NSString *)aType error:(NSError **)outError
1766{
1767 NSStringEncoding encoding = [BDSKStringEncodingManager defaultEncoding];
1768 return [self readFromURL:absoluteURL ofType:aType encoding:encoding error:outError];
1769}
1770
1771- (BOOL)readFromURL:(NSURL *)absoluteURL ofType:(NSString *)aType encoding:(NSStringEncoding)encoding error:(NSError **)outError
1772{
1773 BOOL success;
1774 NSError *error = nil( ( void * ) 0 );
1775 NSData *data = [NSData dataWithContentsOfURL:absoluteURL options:NSUncachedRead error:&error];
1776 if (nil( ( void * ) 0 ) == data) {
1777 if (outError) *outError = error;
1778 return NO( BOOL ) 0;
1779 }
1780
1781 // make sure we clear all macros and groups that are saved in the file, should only have those for revert
1782 // better do this here, so we don't remove them when reading the data fails
1783 [macroResolver removeAllMacros];
1784 [groups removeAllNonSharedGroups]; // this also removes spinners and editor windows for external groups
1785 [frontMatter setString:@""];
1786
1787 // This is only a sanity check; an encoding of 0 is not valid, so is a signal we should ignore xattrs; could only check for public.text UTIs, but it will be zero if it was never written (and we don't warn in that case). The user can do many things to make the attribute incorrect, so this isn't very robust.
1788 NSStringEncoding encodingFromFile = [[self mainWindowSetupDictionaryFromExtendedAttributes] unsignedIntForKey:BDSKDocumentStringEncodingKey defaultValue:0];
1789 if (encodingFromFile != 0 && encodingFromFile != encoding) {
1790
1791 int rv;
1792
1793 error = [NSError mutableLocalErrorWithCode:kBDSKStringEncodingError localizedDescription:NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Incorrect encoding"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"Incorrect encoding", @"Message in alert dialog when opening a document with different encoding")];
1794 [error setValue:[NSString stringWithFormat:NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "BibDesk tried to open the document using encoding %@, but it should have been opened with encoding %@."
) value : @ "" table : ( ( void * ) 0 ) ]
(@"BibDesk tried to open the document using encoding %@, but it should have been opened with encoding %@.", @"Informative text in alert dialog when opening a document with different encoding"), [NSString localizedNameOfStringEncoding:encoding], [NSString localizedNameOfStringEncoding:encodingFromFile]] forKey:NSLocalizedRecoverySuggestionErrorKey];
1795 [error setValue:absoluteURL forKey:NSURLErrorKey];
1796 [error setValue:[NSNumber numberWithUnsignedInt:encoding] forKey:NSStringEncodingErrorKey];
1797
1798 // If we allow the user to reopen here, NSDocumentController puts up an open failure here when we return NO from this instance, and the message appears after the successfully opened file is on-screen...which is confusing, to say the least.
1799 NSAlert *encodingAlert = [NSAlert alertWithMessageText:NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Incorrect encoding"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"Incorrect encoding", @"error title when opening file")
1800 defaultButton:NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Cancel"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"Cancel", @"Button title")
1801 alternateButton:NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Ignore"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"Ignore", @"Button title")
1802 otherButton:NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Reopen"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"Reopen", @"Button title")
1803 informativeTextWithFormat:[NSString stringWithFormat:NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "The document will be opened with encoding %@, but it was previously saved with encoding %@. You should cancel opening and then reopen with the correct encoding."
) value : @ "" table : ( ( void * ) 0 ) ]
(@"The document will be opened with encoding %@, but it was previously saved with encoding %@. You should cancel opening and then reopen with the correct encoding.", @"Informative text in alert dialog when opening a document with different encoding"), [NSString localizedNameOfStringEncoding:encoding], [NSString localizedNameOfStringEncoding:encodingFromFile]]];
1804 rv = [encodingAlert runModal];
1805
1806 if (rv == NSAlertDefaultReturn) {
1807 // the user said to give up
1808 if (outError) *outError = error;
1809 return NO( BOOL ) 0;
1810 }else if (rv == NSAlertAlternateReturn){
1811 NSLog(@"User ignored encoding alert");
1812 }else if (rv == NSAlertOtherReturn){
1813 // we just use the encoding whach was used for saving
1814 encoding = encodingFromFile;
1815 }
1816 }
1817
1818 if ([aType isEqualToString:BDSKBibTeXDocumentType] || [aType isEqualToUTI:[[NSWorkspace sharedWorkspace] UTIForPathExtension:@"bib"]]){
1819 success = [self readFromBibTeXData:data fromURL:absoluteURL encoding:encoding error:&error];
1820 }else if([aType isEqualToString:BDSKRISDocumentType] || [aType isEqualToUTI:[[NSWorkspace sharedWorkspace] UTIForPathExtension:@"ris"]]){
1821 success = [self readFromData:data ofStringType:BDSKRISStringType fromURL:absoluteURL encoding:encoding error:&error];
1822 }else{
1823 // sniff the string to see what format we got
1824 NSString *string = [[[NSString alloc] initWithData:data encoding:encoding] autorelease];
1825 if(string == nil( ( void * ) 0 )){
1826 OFErrorWithInfo_OFError ( & error , @ "edu.ucsd.cs.mmccrack.bibdesk" , kBDSKParserFailed
, "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/BibDocument.m"
, 1826 , NSLocalizedDescriptionKey , [ [ NSBundle mainBundle
] localizedStringForKey : ( @ "Unable To Open Document" ) value
: @ "" table : ( ( void * ) 0 ) ] , NSLocalizedRecoverySuggestionErrorKey
, [ [ NSBundle mainBundle ] localizedStringForKey : ( @ "This document does not appear to be a text file."
) value : @ "" table : ( ( void * ) 0 ) ] , ( ( void * ) 0 )
)
(&error, kBDSKParserFailed, NSLocalizedDescriptionKey, NSLocalizedString(@"Unable To Open Document", @"Error description"), NSLocalizedRecoverySuggestionErrorKey, NSLocalizedString(@"This document does not appear to be a text file.", @"Error informative text"), nil);
1827 if(outError) *outError = error;
1828
1829 // bypass the partial data warning, since we have no data
1830 return NO( BOOL ) 0;
1831 }
1832 int type = [string contentStringType];
1833 if(type == BDSKBibTeXStringType){
1834 success = [self readFromBibTeXData:data fromURL:absoluteURL encoding:encoding error:&error];
1835 }else if (type == BDSKNoKeyBibTeXStringType){
1836 OFErrorWithInfo_OFError ( & error , @ "edu.ucsd.cs.mmccrack.bibdesk" , kBDSKParserFailed
, "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/BibDocument.m"
, 1836 , NSLocalizedDescriptionKey , [ [ NSBundle mainBundle
] localizedStringForKey : ( @ "Unable To Open Document" ) value
: @ "" table : ( ( void * ) 0 ) ] , NSLocalizedRecoverySuggestionErrorKey
, [ [ NSBundle mainBundle ] localizedStringForKey : ( @ "This file appears to contain invalid BibTeX because of missing cite keys. Try to open using temporary cite keys to fix this."
) value : @ "" table : ( ( void * ) 0 ) ] , ( ( void * ) 0 )
)
(&error, kBDSKParserFailed, NSLocalizedDescriptionKey, NSLocalizedString(@"Unable To Open Document", @"Error description"), NSLocalizedRecoverySuggestionErrorKey, NSLocalizedString(@"This file appears to contain invalid BibTeX because of missing cite keys. Try to open using temporary cite keys to fix this.", @"Error informative text"), nil);
1837 if (outError) *outError = error;
1838
1839 // bypass the partial data warning; we have no data in this case
1840 return NO( BOOL ) 0;
1841 }else if (type == BDSKUnknownStringType){
1842 OFErrorWithInfo_OFError ( & error , @ "edu.ucsd.cs.mmccrack.bibdesk" , kBDSKParserFailed
, "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/BibDocument.m"
, 1842 , NSLocalizedDescriptionKey , [ [ NSBundle mainBundle
] localizedStringForKey : ( @ "Unable To Open Document" ) value
: @ "" table : ( ( void * ) 0 ) ] , NSLocalizedRecoverySuggestionErrorKey
, [ [ NSBundle mainBundle ] localizedStringForKey : ( @ "This text file does not contain a recognized data type."
) value : @ "" table : ( ( void * ) 0 ) ] , ( ( void * ) 0 )
)
(&error, kBDSKParserFailed, NSLocalizedDescriptionKey, NSLocalizedString(@"Unable To Open Document", @"Error description"), NSLocalizedRecoverySuggestionErrorKey, NSLocalizedString(@"This text file does not contain a recognized data type.", @"Error informative text"), nil);
1843 if (outError) *outError = error;
1844
1845 // bypass the partial data warning; we have no data in this case
1846 return NO( BOOL ) 0;
1847 }else{
1848 success = [self readFromData:data ofStringType:type fromURL:absoluteURL encoding:encoding error:&error];
1849 }
1850
1851 }
1852
1853 // @@ move this to NSDocumentController; need to figure out where to add it, though
1854 if(success == NO( BOOL ) 0){
1855 int rv;
1856 // run a modal dialog asking if we want to use partial data or give up
1857 NSAlert *alert = [NSAlert alertWithMessageText:[error localizedDescription] ? [error localizedDescription] : NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Error reading file!"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"Error reading file!", @"Message in alert dialog when unable to read file")
1858 defaultButton:NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Give Up"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"Give Up", @"Button title")
1859 alternateButton:NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Edit File"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"Edit File", @"Button title")
1860 otherButton:NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Keep Going"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"Keep Going", @"Button title")
1861 informativeTextWithFormat:NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "There was a problem reading the file. Do you want to give up, edit the file to correct the errors, or keep going with everything that could be analyzed?\n\nIf you choose \"Keep Going\" and then save the file, you will probably lose data."
) value : @ "" table : ( ( void * ) 0 ) ]
(@"There was a problem reading the file. Do you want to give up, edit the file to correct the errors, or keep going with everything that could be analyzed?\n\nIf you choose \"Keep Going\" and then save the file, you will probably lose data.", @"Informative text in alert dialog")];
1862 [alert setAlertStyle:NSCriticalAlertStyle];
1863 rv = [alert runModal];
1864 if (rv == NSAlertDefaultReturn) {
1865 // the user said to give up
1866 [[BDSKErrorObjectController sharedErrorObjectController] documentFailedLoad:self shouldEdit:NO( BOOL ) 0]; // this hands the errors to a new error editor and sets that as the documentForErrors
1867 }else if (rv == NSAlertAlternateReturn){
1868 // the user said to edit the file.
1869 [[BDSKErrorObjectController sharedErrorObjectController] documentFailedLoad:self shouldEdit:YES( BOOL ) 1]; // this hands the errors to a new error editor and sets that as the documentForErrors
1870 }else if(rv == NSAlertOtherReturn){
1871 // the user said to keep going, so if they save, they might clobber data...
1872 // if we don't return YES, NSDocumentController puts up its lame alert saying the document could not be opened, and we get no partial data
1873 success = YES( BOOL ) 1;
1874 }
1875 }
1876 if(outError) *outError = error;
1877 return success;
1878}
1879
1880- (BOOL)readFromBibTeXData:(NSData *)data fromURL:(NSURL *)absoluteURL encoding:(NSStringEncoding)encoding error:(NSError **)outError {
1881 NSString *filePath = [absoluteURL path];
1882 NSStringEncoding parserEncoding = [[BDSKStringEncodingManager sharedEncodingManager] isUnparseableEncoding:encoding] ? NSUTF8StringEncoding : encoding;
1883
1884 [self setDocumentStringEncoding:encoding];
1885
1886 if(parserEncoding != encoding){
1887 NSString *string = [[NSString alloc] initWithData:data encoding:encoding];
1888 if([string canBeConvertedToEncoding:NSUTF8StringEncoding]){
1889 data = [string dataUsingEncoding:NSUTF8StringEncoding];
1890 filePath = [[NSFileManager defaultManager] temporaryFileWithBasename:[filePath lastPathComponent]];
1891 [data writeToFile:filePath atomically:YES( BOOL ) 1];
1892 [string release];
1893 }else{
1894 parserEncoding = encoding;
1895 NSLog(@"Unable to convert data from encoding %@ to UTF-8", [NSString localizedNameOfStringEncoding:encoding]);
1896 }
1897 }
1898
1899 NSError *error = nil( ( void * ) 0 );
1900 BOOL isPartialData;
1901 NSArray *newPubs = [BDSKBibTeXParser itemsFromData:data frontMatter:frontMatter filePath:filePath document:self encoding:parserEncoding isPartialData:&isPartialData error:&error];
1902 if(isPartialData && outError) *outError = error;
1903 [self setPublicationsWithoutUndo:newPubs];
1904
1905 return isPartialData == NO( BOOL ) 0;
1906}
1907
1908- (BOOL)readFromData:(NSData *)data ofStringType:(int)type fromURL:(NSURL *)absoluteURL encoding:(NSStringEncoding)encoding error:(NSError **)outError {
1909
1910 NSError *error = nil( ( void * ) 0 );
1911 NSString *dataString = [[[NSString alloc] initWithData:data encoding:encoding] autorelease];
1912 NSArray *newPubs = nil( ( void * ) 0 );
1913
1914 if(dataString == nil( ( void * ) 0 )){
1915 OFErrorWithInfo_OFError ( & error , @ "edu.ucsd.cs.mmccrack.bibdesk" , kBDSKParserFailed
, "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/BibDocument.m"
, 1915 , NSLocalizedDescriptionKey , [ [ NSBundle mainBundle
] localizedStringForKey : ( @ "Unable to Interpret" ) value :
@ "" table : ( ( void * ) 0 ) ] , NSLocalizedRecoverySuggestionErrorKey
, [ NSString stringWithFormat : [ [ NSBundle mainBundle ] localizedStringForKey
: ( @ "Unable to interpret data as %@. Try a different encoding."
) value : @ "" table : ( ( void * ) 0 ) ] , [ NSString localizedNameOfStringEncoding
: encoding ] ] , NSStringEncodingErrorKey , [ NSNumber numberWithInt
: encoding ] , ( ( void * ) 0 ) )
(&error, kBDSKParserFailed, NSLocalizedDescriptionKey, NSLocalizedString(@"Unable to Interpret", @"Error description"), NSLocalizedRecoverySuggestionErrorKey, [NSString stringWithFormat:NSLocalizedString(@"Unable to interpret data as %@. Try a different encoding.", @"Error informative text"), [NSString localizedNameOfStringEncoding:encoding]], NSStringEncodingErrorKey, [NSNumber numberWithInt:encoding], nil);
1916 if(outError) *outError = error;
1917 return NO( BOOL ) 0;
1918 }
1919
1920 newPubs = [BDSKStringParser itemsFromString:dataString ofType:type error:&error];
1921
1922 if(outError) *outError = error;
1923 [self setPublicationsWithoutUndo:newPubs];
1924
1925 // since we can't save other files in their native format (BibTeX is handled separately)
1926 if (type != BDSKRISStringType)
1927 [self setFileName:nil( ( void * ) 0 )];
1928
1929 return newPubs != nil( ( void * ) 0 );
1930}
1931
1932#pragma mark -
1933
1934- (void)setDocumentStringEncoding:(NSStringEncoding)encoding{
1935 docState.documentStringEncoding = encoding;
1936}
1937
1938- (NSStringEncoding)documentStringEncoding{
1939 return docState.documentStringEncoding;
1940}
1941
1942#pragma mark -
1943
1944- (void)temporaryCiteKeysAlertDidEnd:(NSAlert *)alert returnCode:(int)returnCode contextInfo:(void *)contextInfo {
1945 NSString *tmpKey = [(NSString *)contextInfo autorelease];
1946 if(returnCode == NSAlertDefaultReturn){
1947 NSArray *selItems = [self selectedPublications];
1948 [self selectPublications:[[self publications] allItemsForCiteKey:tmpKey]];
1949 [self generateCiteKeysForPublications:[self selectedPublications]];
1950 [self selectPublications:selItems];
1951 }
1952}
1953
1954- (void)reportTemporaryCiteKeys:(NSString *)tmpKey forNewDocument:(BOOL)isNew{
1955 if([publications count] == 0)
1956 return;
1957
1958 NSArray *tmpKeyItems = [[self publications] allItemsForCiteKey:tmpKey];
1959
1960 if([tmpKeyItems count] == 0)
1961 return;
1962
1963 if(isNew)
1964 [self selectPublications:tmpKeyItems];
1965
1966 NSString *infoFormat = isNew ? NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "This document was opened using the temporary cite key \"%@\" for the selected publications. In order to use your file with BibTeX, you must generate valid cite keys for all of these items. Do you want me to do this now?"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"This document was opened using the temporary cite key \"%@\" for the selected publications. In order to use your file with BibTeX, you must generate valid cite keys for all of these items. Do you want me to do this now?", @"Informative text in alert dialog")
1967 : NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "New items are added using the temporary cite key \"%@\". In order to use your file with BibTeX, you must generate valid cite keys for these items. Do you want me to do this now?"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"New items are added using the temporary cite key \"%@\". In order to use your file with BibTeX, you must generate valid cite keys for these items. Do you want me to do this now?", @"Informative text in alert dialog");
1968
1969 NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Temporary Cite Keys"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"Temporary Cite Keys", @"Message in alert dialog when opening a file with temporary cite keys")
1970 defaultButton:NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Generate"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"Generate", @"Button title")
1971 alternateButton:NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Don't Generate"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"Don't Generate", @"Button title")
1972 otherButton:nil( ( void * ) 0 )
1973 informativeTextWithFormat:infoFormat, tmpKey];
1974
1975 [alert beginSheetModalForWindow:documentWindow
1976 modalDelegate:self
1977 didEndSelector:@selector(temporaryCiteKeysAlertDidEnd:returnCode:contextInfo:)
1978 contextInfo:[tmpKey retain]];
1979}
1980
1981#pragma mark -
1982#pragma mark String representations
1983
1984- (NSString *)bibTeXStringForPublications:(NSArray *)items{
1985 return [self bibTeXStringDroppingInternal:NO( BOOL ) 0 forPublications:items];
1986}
1987
1988- (NSString *)bibTeXStringDroppingInternal:(BOOL)drop forPublications:(NSArray *)items{
1989 NSMutableString *s = [NSMutableString string];
1990 NSEnumerator *e = [items objectEnumerator];
1991 BibItem *pub;
1992 int options = 0;
1993
1994 if ([[OFPreferenceWrapper sharedPreferenceWrapper] boolForKey:BDSKShouldTeXifyWhenSavingAndCopyingKey])
1995 options |= BDSKBibTeXOptionTeXifyMask;
1996 if (drop)
1997 options |= BDSKBibTeXOptionDropInternalMask;
1998
1999 while(pub = [e nextObject])
2000 [s appendStrings:@"\n", [pub bibTeXStringWithOptions:options], @"\n", nil( ( void * ) 0 )];
2001
2002 return s;
2003}
2004
2005- (NSString *)previewBibTeXStringForPublications:(NSArray *)items{
2006
2007 if([items count]) NSParameterAssertdo { if ( ! ( ( [ [ items objectAtIndex : 0 ] isKindOfClass :
[ BibItem class ] ] ) ) ) { [ [ NSAssertionHandler currentHandler
] handleFailureInMethod : _cmd object : self file : [ NSString
stringWithUTF8String : "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/BibDocument.m"
] lineNumber : 2007 description : ( @ "Invalid parameter not satisfying: %s"
) , ( "[[items objectAtIndex:0] isKindOfClass:[BibItem class]]"
) , ( 0 ) , ( 0 ) , ( 0 ) , ( 0 ) ] ; } } while ( 0 )
([[items objectAtIndex:0] isKindOfClass:[BibItem class]]);
2008
2009 unsigned numberOfPubs = [items count];
2010 NSMutableString *bibString = [[NSMutableString alloc] initWithCapacity:(numberOfPubs * 100)];
2011
2012 int options = BDSKBibTeXOptionDropLinkedURLsMask;
2013 if ([[OFPreferenceWrapper sharedPreferenceWrapper] boolForKey:BDSKShouldTeXifyWhenSavingAndCopyingKey])
2014 options |= BDSKBibTeXOptionTeXifyMask;
2015
2016 // in case there are @preambles in it
2017 [bibString appendString:frontMatter];
2018 [bibString appendString:@"\n"];
2019
2020 [bibString appendString:[[BDSKMacroResolver defaultMacroResolver] bibTeXString]];
2021 [bibString appendString:[[self macroResolver] bibTeXString]];
2022
2023 NSEnumerator *e = [items objectEnumerator];
2024 BibItem *aPub = nil( ( void * ) 0 );
2025 BibItem *aParent = nil( ( void * ) 0 );
2026 NSMutableArray *selItems = [[NSMutableArray alloc] initWithCapacity:numberOfPubs];
2027 NSMutableSet *parentItems = [[NSMutableSet alloc] initWithCapacity:numberOfPubs];
2028 NSMutableArray *selParentItems = [[NSMutableArray alloc] initWithCapacity:numberOfPubs];
2029
2030 while(aPub = [e nextObject]){
2031 [selItems addObject:aPub];
2032
2033 if(aParent = [aPub crossrefParent])
2034 [parentItems addObject:aParent];
2035 }
2036
2037 e = [selItems objectEnumerator];
2038 while(aPub = [e nextObject]){
2039 if([parentItems containsObject:aPub]){
2040 [parentItems removeObject:aPub];
2041 [selParentItems addObject:aPub];
2042 }else{
2043 [bibString appendString:[aPub bibTeXStringWithOptions:options]];
2044 }
2045 }
2046
2047 e = [selParentItems objectEnumerator];
2048 while(aPub = [e nextObject]){
2049 [bibString appendString:[aPub bibTeXStringWithOptions:options]];
2050 }
2051
2052 e = [parentItems objectEnumerator];
2053 while(aPub = [e nextObject]){
2054 [bibString appendString:[aPub bibTeXStringWithOptions:options]];
2055 }
2056
2057 [selItems release];
2058 [parentItems release];
2059 [selParentItems release];
2060
2061 return [bibString autorelease];
2062}
2063
2064- (NSString *)RISStringForPublications:(NSArray *)items{
2065 NSMutableString *s = [NSMutableString string];
2066 NSEnumerator *e = [items objectEnumerator];
2067 BibItem *pub;
2068
2069 while(pub = [e nextObject]){
2070 [s appendString:@"\n"];
2071 [s appendString:[pub RISStringValue]];
2072 [s appendString:@"\n"];
2073 }
2074
2075 return s;
2076}
2077
2078- (NSString *)citeStringForPublications:(NSArray *)items citeString:(NSString *)citeString{
2079 OFPreferenceWrapper *sud = [OFPreferenceWrapper sharedPreferenceWrapper];
2080 BOOL prependTilde = [sud boolForKey:BDSKCitePrependTildeKey];
2081 NSString *startCite = [NSString stringWithFormat:@"%@\\%@%@", (prependTilde? @"~" : @""), citeString, [sud stringForKey:BDSKCiteStartBracketKey]];
2082 NSString *endCite = [sud stringForKey:BDSKCiteEndBracketKey];
2083 NSMutableString *s = [NSMutableString stringWithString:startCite];
2084
2085 BOOL sep = [sud boolForKey:BDSKSeparateCiteKey];
2086 NSString *separator = (sep)? [NSString stringWithFormat:@"%@%@", endCite, startCite] : @",";
2087 BibItem *pub;
2088 BOOL first = YES( BOOL ) 1;
2089
2090 if([items count]) NSParameterAssertdo { if ( ! ( ( [ [ items objectAtIndex : 0 ] isKindOfClass :
[ BibItem class ] ] ) ) ) { [ [ NSAssertionHandler currentHandler
] handleFailureInMethod : _cmd object : self file : [ NSString
stringWithUTF8String : "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/BibDocument.m"
] lineNumber : 2090 description : ( @ "Invalid parameter not satisfying: %s"
) , ( "[[items objectAtIndex:0] isKindOfClass:[BibItem class]]"
) , ( 0 ) , ( 0 ) , ( 0 ) , ( 0 ) ] ; } } while ( 0 )
([[items objectAtIndex:0] isKindOfClass:[BibItem class]]);
2091
2092 NSEnumerator *e = [items objectEnumerator];
2093 while(pub = [e nextObject]){
2094 if(first) first = NO( BOOL ) 0;
2095 else [s appendString:separator];
2096 [s appendString:[pub citeKey]];
2097 }
2098 [s appendString:endCite];
2099
2100 return s;
2101}
2102
2103#pragma mark -
2104#pragma mark New publications from pasteboard
2105
2106- (void)addPublications:(NSArray *)newPubs publicationsToAutoFile:(NSArray *)pubsToAutoFile temporaryCiteKey:(NSString *)tmpCiteKey selectLibrary:(BOOL)shouldSelect edit:(BOOL)shouldEdit {
2107 NSEnumerator *pubEnum;
2108 BibItem *pub;
2109
2110 if (shouldSelect)
2111 [self selectLibraryGroup:nil( ( void * ) 0 )];
2112 [self addPublications:newPubs];
2113 if ([self hasLibraryGroupSelected])
2114 [self selectPublications:newPubs];
2115 if (pubsToAutoFile != nil( ( void * ) 0 )){
2116 // tried checking [pb isEqual:[NSPasteboard pasteboardWithName:NSDragPboard]] before using delay, but pb is a CFPasteboardUnique
2117 pubEnum = [pubsToAutoFile objectEnumerator];
2118 while (pub = [pubEnum nextObject])
2119 [pub performSelector:@selector(autoFileLinkedFile:) withObjectsFromArray:[pub localFiles]];
2120 }
2121
2122 BOOL autoGenerate = [[OFPreferenceWrapper sharedPreferenceWrapper] boolForKey:BDSKCiteKeyAutogenerateKey];
2123 NSMutableArray *pubs = [NSMutableArray arrayWithCapacity:[newPubs count]];
2124
2125 pubEnum = [newPubs objectEnumerator];
2126
2127 while (pub = [pubEnum nextObject]) {
2128 if ((autoGenerate == NO( BOOL ) 0 && [pub hasEmptyOrDefaultCiteKey]) ||
2129 (autoGenerate && [pub canGenerateAndSetCiteKey])) // @@ or should we check for hasEmptyOrDefaultCiteKey ?
2130 [pubs addObject:pub];
2131 }
2132 [self generateCiteKeysForPublications:pubs];
2133
2134 // set Date-Added to the current date, since unarchived items will have their own (incorrect) date
2135 NSCalendarDate *importDate = [NSCalendarDate date];
2136 [newPubs makeObjectsPerformSelector:@selector(setField:toValue:) withObject:BDSKDateAddedString withObject:[importDate description]];
2137
2138 if(shouldEdit) {
2139 [self editPublications:newPubs]; // this will ask the user when there are many pubs
2140 }
2141
2142 [[self undoManager] setActionName:NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Add Publication"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"Add Publication", @"Undo action name")];
2143
2144 NSMutableArray *importedItems = [NSMutableArray array];
2145 if (shouldSelect == NO( BOOL ) 0 && docState.didImport)
2146 [importedItems addObjectsFromArray:[[groups lastImportGroup] publications]];
2147 docState.didImport = (shouldSelect == NO( BOOL ) 0);
2148 [importedItems addObjectsFromArray:newPubs];
2149
2150 // set up the smart group that shows the latest import
2151 // @@ do this for items added via the editor? doesn't seem as useful
2152 [groups setLastImportedPublications:importedItems];
2153
2154 [[BDSKScriptHookManager sharedManager] runScriptHookWithName:BDSKImportPublicationsScriptHookName forPublications:newPubs document:self];
2155
2156 if(tmpCiteKey != nil( ( void * ) 0 ))
2157 [self reportTemporaryCiteKeys:tmpCiteKey forNewDocument:NO( BOOL ) 0];
2158}
2159
2160- (BOOL)addPublicationsFromPasteboard:(NSPasteboard *)pb selectLibrary:(BOOL)shouldSelect verbose:(BOOL)verbose error:(NSError **)outError{
2161 // these are the types we support, the order here is important!
2162 NSString *type = [pb availableTypeFromArray:[NSArray arrayWithObjects:BDSKBibItemPboardType, BDSKWeblocFilePboardType, BDSKReferenceMinerStringPboardType, NSStringPboardType, NSFilenamesPboardType, NSURLPboardType, nil( ( void * ) 0 )]];
2163 NSArray *newPubs = nil( ( void * ) 0 );
2164 NSArray *newFilePubs = nil( ( void * ) 0 );
2165 NSError *error = nil( ( void * ) 0 );
2166 NSString *temporaryCiteKey = nil( ( void * ) 0 );
2167 BOOL shouldEdit = [[OFPreferenceWrapper sharedPreferenceWrapper] boolForKey:BDSKEditOnPasteKey];
2168
2169 if([type isEqualToString:BDSKBibItemPboardType]){
2170 NSData *pbData = [pb dataForType:BDSKBibItemPboardType];
2171 newPubs = [self newPublicationsFromArchivedData:pbData];
2172 } else if([type isEqualToString:BDSKReferenceMinerStringPboardType]){ // pasteboard type from Reference Miner, determined using Pasteboard Peeker
2173 NSString *pbString = [pb stringForType:BDSKReferenceMinerStringPboardType];
2174 // sniffing the string for RIS is broken because RefMiner puts junk at the beginning
2175 newPubs = [self newPublicationsForString:pbString type:BDSKReferenceMinerStringType verbose:verbose error:&error];
2176 if(temporaryCiteKey = [[error userInfo] valueForKey:@"temporaryCiteKey"])
2177 error = nil( ( void * ) 0 ); // accept temporary cite keys, but show a warning later
2178 }else if([type isEqualToString:NSStringPboardType]){
2179 NSString *pbString = [pb stringForType:NSStringPboardType];
2180 // sniff the string to see what its type is
2181 newPubs = [self newPublicationsForString:pbString type:BDSKUnknownStringType verbose:verbose error:&error];
2182 if(temporaryCiteKey = [[error userInfo] valueForKey:@"temporaryCiteKey"])
2183 error = nil( ( void * ) 0 ); // accept temporary cite keys, but show a warning later
2184 }else if([type isEqualToString:NSFilenamesPboardType]){
2185 NSArray *pbArray = [pb propertyListForType:NSFilenamesPboardType]; // we will get an array
2186 // try this first, in case these files are a type we can open
2187 NSMutableArray *unparseableFiles = [[NSMutableArray alloc] initWithCapacity:[pbArray count]];
2188 newPubs = [self extractPublicationsFromFiles:pbArray unparseableFiles:unparseableFiles verbose:verbose error:&error];
2189 if(temporaryCiteKey = [[error userInfo] objectForKey:@"temporaryCiteKey"])
2190 error = nil( ( void * ) 0 ); // accept temporary cite keys, but show a warning later
2191 if ([unparseableFiles count] > 0) {
2192 newFilePubs = [self newPublicationsForFiles:unparseableFiles error:&error];
2193 newPubs = [newPubs arrayByAddingObjectsFromArray:newFilePubs];
2194 }
2195 [unparseableFiles release];
2196 }else if([type isEqualToString:BDSKWeblocFilePboardType]){
2197 NSURL *pbURL = [NSURL URLWithString:[pb stringForType:BDSKWeblocFilePboardType]];
2198 if([pbURL isFileURL])
2199 newPubs = newFilePubs = [self newPublicationsForFiles:[NSArray arrayWithObject:[pbURL path]] error:&error];
2200 else
2201 newPubs = [self newPublicationForURLFromPasteboard:pb error:&error];
2202 }else if([type isEqualToString:NSURLPboardType]){
2203 NSURL *pbURL = [NSURL URLFromPasteboard:pb];
2204 if([pbURL isFileURL])
2205 newPubs = newFilePubs = [self newPublicationsForFiles:[NSArray arrayWithObject:[pbURL path]] error:&error];
2206 else
2207 newPubs = [self newPublicationForURLFromPasteboard:pb error:&error];
2208 }else{
2209 // errors are key, value
2210 OFErrorWithInfo_OFError ( & error , @ "edu.ucsd.cs.mmccrack.bibdesk" , kBDSKParserFailed
, "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/BibDocument.m"
, 2210 , NSLocalizedDescriptionKey , [ [ NSBundle mainBundle
] localizedStringForKey : ( @ "Did not find anything appropriate on the pasteboard"
) value : @ "" table : ( ( void * ) 0 ) ] , ( ( void * ) 0 )
)
(&error, kBDSKParserFailed, NSLocalizedDescriptionKey, NSLocalizedString(@"Did not find anything appropriate on the pasteboard", @"Error description"), nil);
2211 }
2212
2213 if (newPubs == nil( ( void * ) 0 )){
2214 if(outError) *outError = error;
2215 return NO( BOOL ) 0;
2216 }else if ([newPubs count] > 0)
2217 [self addPublications:newPubs publicationsToAutoFile:newFilePubs temporaryCiteKey:temporaryCiteKey selectLibrary:shouldSelect edit:shouldEdit];
2218
2219 return YES( BOOL ) 1;
2220}
2221
2222- (BOOL)addPublicationsFromFile:(NSString *)fileName verbose:(BOOL)verbose error:(NSError **)outError{
2223 NSError *error = nil( ( void * ) 0 );
2224 NSString *temporaryCiteKey = nil( ( void * ) 0 );
2225 NSArray *newPubs = [self extractPublicationsFromFiles:[NSArray arrayWithObject:fileName] unparseableFiles:nil( ( void * ) 0 ) verbose:verbose error:&error];
2226 BOOL shouldEdit = [[OFPreferenceWrapper sharedPreferenceWrapper] boolForKey:BDSKEditOnPasteKey];
2227
2228 if(temporaryCiteKey = [[error userInfo] valueForKey:@"temporaryCiteKey"])
2229 error = nil( ( void * ) 0 ); // accept temporary cite keys, but show a warning later
2230
2231 if([newPubs count] == 0){
2232 if(outError) *outError = error;
2233 return NO( BOOL ) 0;
2234 }
2235
2236 [self addPublications:newPubs publicationsToAutoFile:nil( ( void * ) 0 ) temporaryCiteKey:temporaryCiteKey selectLibrary:YES( BOOL ) 1 edit:shouldEdit];
2237
2238 return YES( BOOL ) 1;
2239}
2240
2241- (NSArray *)newPublicationsFromArchivedData:(NSData *)data{
2242 NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
2243
2244 [BDSKComplexString setMacroResolverForUnarchiving:macroResolver];
2245
2246 NSArray *newPubs = [unarchiver decodeObjectForKey:@"publications"];
2247 [unarchiver finishDecoding];
2248 [unarchiver release];
2249
2250 [BDSKComplexString setMacroResolverForUnarchiving:nil( ( void * ) 0 )];
2251
2252 return newPubs;
2253}
2254
2255// pass BDSKUnkownStringType to allow BDSKStringParser to sniff the text and determine the format
2256- (NSArray *)newPublicationsForString:(NSString *)string type:(int)type verbose:(BOOL)verbose error:(NSError **)outError {
2257 NSArray *newPubs = nil( ( void * ) 0 );
2258 NSError *parseError = nil( ( void * ) 0 );
2259 BOOL isPartialData = NO( BOOL ) 0;
2260
2261 // @@ BDSKStringParser doesn't handle any BibTeX types, so it's not really useful as a funnel point for any string type, since each usage requires special casing for BibTeX.
2262 if(BDSKUnknownStringType == type)
2263 type = [string contentStringType];
2264
2265 if(type == BDSKBibTeXStringType){
2266 newPubs = [BDSKBibTeXParser itemsFromString:string document:self isPartialData:&isPartialData error:&parseError];
2267 }else if(type == BDSKNoKeyBibTeXStringType){
2268 newPubs = [BDSKBibTeXParser itemsFromString:[string stringWithPhoneyCiteKeys:@"FixMe"] document:self isPartialData:&isPartialData error:&parseError];
2269 }else {
2270 // this will create the NSError if the type is unrecognized
2271 newPubs = [BDSKStringParser itemsFromString:string ofType:type error:&parseError];
2272 }
2273
2274 if(nil( ( void * ) 0 ) == newPubs || isPartialData) {
2275
2276 if (verbose) {
2277 // @@ should just be able to create an alert from the NSError, unless it's unknown type
2278 NSString *message = nil( ( void * ) 0 );
2279 NSString *defaultButton = NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Cancel"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"Cancel", @"");
2280 NSString *alternateButton = nil( ( void * ) 0 );
2281 NSString *otherButton = nil( ( void * ) 0 );
2282 NSString *alertTitle = NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Error Reading String"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"Error Reading String", @"Message in alert dialog when failing to parse dropped or copied string");
2283 int errorCode = [parseError code];
2284
2285 // the partial data alert only applies to BibTeX; we could show the editor window for non-BibTeX data (I think...), but we also have to deal with alerts being shown twice if NSError is involved
2286 if(type == BDSKBibTeXStringType || type == BDSKNoKeyBibTeXStringType){
2287 // here we want to display an alert, but don't propagate a nil/error back up, since it's not a failure
2288 if (errorCode == kBDSKParserIgnoredFrontMatter) {
2289 message = [parseError localizedRecoverySuggestion];
2290 alertTitle = [parseError localizedDescription];
2291 defaultButton = nil( ( void * ) 0 );
2292 // @@ fixme: NSError
2293 parseError = nil( ( void * ) 0 );
2294 } else {
2295 // this was BibTeX, but the user may want to try going with partial data
2296 message = NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "There was a problem inserting the data. Do you want to ignore this data, open a window containing the data to edit it and remove the errors, or keep going and use everything that BibDesk could parse?\n(It's likely that choosing \"Keep Going\" will lose some data.)"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"There was a problem inserting the data. Do you want to ignore this data, open a window containing the data to edit it and remove the errors, or keep going and use everything that BibDesk could parse?\n(It's likely that choosing \"Keep Going\" will lose some data.)", @"Informative text in alert dialog");
2297 alternateButton = NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Edit data"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"Edit data", @"Button title");
2298 otherButton = NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Keep going"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"Keep going", @"Button title");
2299 }
2300
2301 // run a modal dialog asking if we want to use partial data or give up
2302 NSAlert *alert = [NSAlert alertWithMessageText:alertTitle
2303 defaultButton:defaultButton
2304 alternateButton:alternateButton
2305 otherButton:otherButton
2306 informativeTextWithFormat:message];
2307 int rv = [alert runModal];
2308
2309 if(rv == NSAlertDefaultReturn && errorCode != kBDSKParserIgnoredFrontMatter){
2310 // the user said to give up
2311 newPubs = nil( ( void * ) 0 );
2312 }else if (rv == NSAlertAlternateReturn){
2313 // they said to edit the file.
2314 [[BDSKErrorObjectController sharedErrorObjectController] showEditorForLastPasteDragError];
2315 newPubs = nil( ( void * ) 0 );
2316 }else if(rv == NSAlertOtherReturn){
2317 // the user said to keep going, so if they save, they might clobber data...
2318 // @@ should we ignore the error as well?
2319 }
2320
2321 }
2322
2323 // if not BibTeX, it's an unknown type or failed due to parser error; in either case, we must have a valid NSError since the parser returned nil
2324 // no partial data here since that only applies to BibTeX parsing; all we can do is just return nil and propagate the error back up, although I suppose we could display the error editor...
2325 } else {
2326 newPubs = nil( ( void * ) 0 );
2327 }
2328
2329 }else if(type == BDSKNoKeyBibTeXStringType){
2330
2331 OBASSERTdo { if ( ! ( parseError == ( ( void * ) 0 ) ) ) OBAssertFailed
( "ASSERT" , "parseError == nil" , "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/BibDocument.m"
, 2331 ) ; } while ( ( BOOL ) 0 )
(parseError == nil);
2332
2333 // return an error when we inserted temporary keys, let the caller decide what to do with it
2334 // don't override a parseError though, as that is probably more relevant
2335 OFErrorWithInfo_OFError ( & parseError , @ "edu.ucsd.cs.mmccrack.bibdesk"
, kBDSKParserFailed , "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/BibDocument.m"
, 2335 , NSLocalizedDescriptionKey , [ [ NSBundle mainBundle
] localizedStringForKey : ( @ "Temporary Cite Keys" ) value :
@ "" table : ( ( void * ) 0 ) ] , @ "temporaryCiteKey" , @ "FixMe"
, ( ( void * ) 0 ) )
(&parseError, kBDSKParserFailed, NSLocalizedDescriptionKey, NSLocalizedString(@"Temporary Cite Keys", @"Error description"), @"temporaryCiteKey", @"FixMe", nil);
2336 }
2337
2338 if(outError) *outError = parseError;
2339 return newPubs;
2340}
2341
2342// sniff the contents of each file, returning them in an array of BibItems, while unparseable files are added to the mutable array passed as a parameter
2343- (NSArray *)extractPublicationsFromFiles:(NSArray *)filenames unparseableFiles:(NSMutableArray *)unparseableFiles verbose:(BOOL)verbose error:(NSError **)outError {
2344 NSEnumerator *e = [filenames objectEnumerator];
2345 NSString *fileName;
2346 NSString *contentString;
2347 NSMutableArray *array = [NSMutableArray array];
2348 int type = BDSKUnknownStringType;
2349
2350 // some common types that people might use as attachments; we don't need to sniff these
2351 NSSet *unreadableTypes = [NSSet caseInsensitiveStringSetWithObjects:@"pdf", @"ps", @"eps", @"doc", @"htm", @"textClipping", @"webloc", @"html", @"rtf", @"tiff", @"tif", @"png", @"jpg", @"jpeg", nil( ( void * ) 0 )];
2352
2353 while(fileName = [e nextObject]){
2354 type = BDSKUnknownStringType;
2355
2356 // we /can/ create a string from these (usually), but there's no point in wasting the memory
2357
2358 NSString *theUTI = [[NSWorkspace sharedWorkspace] UTIForURL:[NSURL fileURLWithPath:fileName]];
2359 if([theUTI isEqualToUTI:@"net.sourceforge.bibdesk.bdsksearch"]){
2360 NSDictionary *dictionary = [NSDictionary dictionaryWithContentsOfFile:fileName];
2361 Class aClass = NSClassFromString([dictionary objectForKey:@"class"]);
2362 BDSKSearchGroup *group = [[[(aClass ? aClass : [BDSKSearchGroup class]) alloc] initWithDictionary:dictionary] autorelease];
2363 if(group)
2364 [groups addSearchGroup:group];
2365 }else if([unreadableTypes containsObject:[fileName pathExtension]]){
2366 [unparseableFiles addObject:fileName];
2367 }else {
2368
2369 // try to create a string
2370 contentString = [[NSString alloc] initWithContentsOfFile:fileName encoding:[self documentStringEncoding] guessEncoding:YES( BOOL ) 1];
2371
2372 if(contentString != nil( ( void * ) 0 )){
2373 if([theUTI isEqualToUTI:@"org.tug.tex.bibtex"])
2374 type = BDSKBibTeXStringType;
2375 else if([theUTI isEqualToUTI:@"net.sourceforge.bibdesk.ris"])
2376 type = BDSKRISStringType;
2377 else
2378 type = [contentString contentStringType];
2379
2380 NSError *parseError = nil( ( void * ) 0 );
2381 NSArray *contentArray = (type == BDSKUnknownStringType) ? nil( ( void * ) 0 ) : [self newPublicationsForString:contentString type:type verbose:verbose error:&parseError];
2382
2383 if(contentArray == nil( ( void * ) 0 )){
2384 // unable to parse, we link the file and can ignore the error
2385 [unparseableFiles addObject:fileName];
2386 } else {
2387 // forward any temporaryCiteKey warning
2388 if(parseError && outError) *outError = parseError;
2389 [array addObjectsFromArray:contentArray];
2390 }
2391
2392 [contentString release];
2393 contentString = nil( ( void * ) 0 );
2394
2395 } else {
2396 // unable to create the string
2397 [unparseableFiles addObject:fileName];
2398 }
2399 }
2400 }
2401
2402 return array;
2403}
2404
2405- (NSArray *)newPublicationsForFiles:(NSArray *)filenames error:(NSError **)error {
2406 NSMutableArray *newPubs = [NSMutableArray arrayWithCapacity:[filenames count]];
2407 NSEnumerator *e = [filenames objectEnumerator];
2408 NSString *fnStr = nil( ( void * ) 0 );
2409 NSURL *url = nil( ( void * ) 0 );
2410
2411 while(fnStr = [e nextObject]){
2412 fnStr = [fnStr stringByStandardizingPath];
2413 if(url = [NSURL fileURLWithPath:fnStr]){
2414 NSError *xerror = nil( ( void * ) 0 );
2415 BibItem *newBI = nil( ( void * ) 0 );
2416
2417 // most reliable metadata should be our private EA
2418 if([[OFPreferenceWrapper sharedPreferenceWrapper] boolForKey:BDSKReadExtendedAttributesKey]){
2419 NSData *btData = [[NSFileManager defaultManager] extendedAttributeNamed:OMNI_BUNDLE_IDENTIFIER@ "edu.ucsd.cs.mmccrack.bibdesk" @".bibtexstring" atPath:fnStr traverseLink:NO( BOOL ) 0 error:&xerror];
2420 if(btData){
2421 NSString *btString = [[NSString alloc] initWithData:btData encoding:NSUTF8StringEncoding];
2422 BOOL isPartialData;
2423 NSArray *items = [BDSKBibTeXParser itemsFromString:btString document:self isPartialData:&isPartialData error:&xerror];
2424 newBI = isPartialData ? nil( ( void * ) 0 ) : [items firstObject];
2425 [btString release];
2426 }
2427 }
2428
2429 // next best metadata source: if the filename is purely decimal digits, try getting it from PubMed
2430 NSString *lastPathComponent = [[fnStr lastPathComponent] stringByDeletingPathExtension];
2431 BOOL tryPubMed = [[OFPreferenceWrapper sharedPreferenceWrapper] boolForKey:BDSKShouldUsePubMedMetadata];
2432 if(newBI == nil( ( void * ) 0 ) && tryPubMed && [lastPathComponent containsCharacterInSet:[NSCharacterSet nonDecimalDigitCharacterSet]] == NO( BOOL ) 0)
2433 newBI = [BibItem itemWithPMID:lastPathComponent];
2434
2435 // fall back on the least reliable metadata source (hidden pref)
2436 if(newBI == nil( ( void * ) 0 ) && [[OFPreferenceWrapper sharedPreferenceWrapper] boolForKey:BDSKShouldUsePDFMetadata])
2437 newBI = [BibItem itemWithPDFMetadata:[PDFMetadata metadataForURL:url error:&xerror]];
2438
2439 if(newBI == nil( ( void * ) 0 ))
2440 newBI = [[[BibItem alloc] init] autorelease];
2441
2442 [newBI addFileForURL:url autoFile:NO( BOOL ) 0 runScriptHook:NO( BOOL ) 0];
2443 [newPubs addObject:newBI];
2444 }
2445 }
2446
2447 return newPubs;
2448}
2449
2450- (NSArray *)newPublicationForURLFromPasteboard:(NSPasteboard *)pboard error:(NSError **)error {
2451
2452 NSMutableArray *pubs = nil( ( void * ) 0 );
2453
2454 NSURL *theURL = [WebView URLFromPasteboard:pboard];
2455 if (theURL) {
2456 BibItem *newBI = [[[BibItem alloc] init] autorelease];
2457 pubs = [NSMutableArray array];
2458 [newBI addFileForURL:theURL autoFile:NO( BOOL ) 0 runScriptHook:YES( BOOL ) 1];
2459 [newBI setPubType:@"webpage"];
2460 [newBI setField:@"Lastchecked" toValue:[[NSCalendarDate date] dateDescription]];
2461 NSString *title = [WebView URLTitleFromPasteboard:pboard];
2462 if (title)
2463 [newBI setField:BDSKTitleString toValue:title];
2464 [pubs addObject:newBI];
2465 } else {
2466 OFErrorWithInfo_OFError ( error , @ "edu.ucsd.cs.mmccrack.bibdesk" , kBDSKParserFailed
, "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/BibDocument.m"
, 2466 , NSLocalizedDescriptionKey , [ [ NSBundle mainBundle
] localizedStringForKey : ( @ "Did not find expected URL on the pasteboard"
) value : @ "" table : ( ( void * ) 0 ) ] , ( ( void * ) 0 )
)
(error, kBDSKParserFailed, NSLocalizedDescriptionKey, NSLocalizedString(@"Did not find expected URL on the pasteboard", @"Error description"), nil);
2467 }
2468
2469 return pubs;
2470}
2471
2472#pragma mark -
2473#pragma mark BDSKItemPasteboardHelper delegate
2474
2475- (void)pasteboardHelperWillBeginGenerating:(BDSKItemPasteboardHelper *)helper{
2476 [self setStatus:[NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Generating data. Please wait"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"Generating data. Please wait", @"Status message when generating drag/paste data") stringByAppendingEllipsis]];
2477 [statusBar startAnimation:nil( ( void * ) 0 )];
2478}
2479
2480- (void)pasteboardHelperDidEndGenerating:(BDSKItemPasteboardHelper *)helper{
2481 [statusBar stopAnimation:nil( ( void * ) 0 )];
2482 [self updateStatus];
2483}
2484
2485- (NSString *)pasteboardHelper:(BDSKItemPasteboardHelper *)pboardHelper bibTeXStringForItems:(NSArray *)items{
2486 return [self previewBibTeXStringForPublications:items];
2487}
2488
2489#pragma mark -
2490#pragma mark Sorting
2491
2492- (void)sortPubsByKey:(NSString *)key{
2493
2494 NSTableColumn *tableColumn = nil( ( void * ) 0 );
2495
2496 // cache the selection; this works for multiple publications
2497 NSArray *pubsToSelect = nil( ( void * ) 0 );
2498 if([tableView numberOfSelectedRows])
2499 pubsToSelect = [shownPublications objectsAtIndexes:[tableView selectedRowIndexes]];
2500
2501 // a nil argument means resort the current column in the same order
2502 if(key == nil( ( void * ) 0 )){
2503 if(sortKey == nil( ( void * ) 0 ))
2504 return;
2505 key = sortKey;
2506 docState.sortDescending = !docState.sortDescending; // we'll reverse this again in the next step
2507 }
2508
2509 tableColumn = [tableView tableColumnWithIdentifier:key];
2510
2511 if ([sortKey isEqualToString:key]) {
2512 // User clicked same column, change sort order
2513 docState.sortDescending = !docState.sortDescending;
2514 } else {
2515 // User clicked new column, change old/new column headers,
2516 // save new sorting selector, and re-sort the array.
2517 docState.sortDescending = [key isEqualToString:BDSKRelevanceString];
2518 if (sortKey)
2519 [tableView setIndicatorImage:nil( ( void * ) 0 ) inTableColumn:[tableView tableColumnWithIdentifier:sortKey]];
2520 if([previousSortKey isEqualToString:sortKey] == NO( BOOL ) 0){
2521 [previousSortKey release];
2522 previousSortKey = sortKey; // this is retained
2523 }else{
2524 [sortKey release];
2525 }
2526 sortKey = [key retain];
2527 [tableView setHighlightedTableColumn:tableColumn];
2528 }
2529
2530 if(previousSortKey == nil( ( void * ) 0 ))
2531 previousSortKey = [sortKey retain];
2532
2533 NSString *userInfo = [self fileName];
2534 NSArray *sortDescriptors = [NSArray arrayWithObjects:[BDSKTableSortDescriptor tableSortDescriptorForIdentifier:sortKey ascending:!docState.sortDescending userInfo:userInfo], [BDSKTableSortDescriptor tableSortDescriptorForIdentifier:previousSortKey ascending:!docState.sortDescending userInfo:userInfo], nil( ( void * ) 0 )];
2535 [tableView setSortDescriptors:sortDescriptors]; // just using this to store them; it's really a no-op
2536
2537
2538 // @@ DON'T RETURN WITHOUT RESETTING THIS!
2539 // this is a hack to keep us from getting selection change notifications while sorting (which updates the TeX and attributed text previews)
2540 [tableView setDelegate:nil( ( void * ) 0 )];
2541
2542 // sort by new primary column, subsort with previous primary column
2543 [shownPublications mergeSortUsingDescriptors:sortDescriptors];
2544
2545 // Set the graphic for the new column header
2546 [tableView setIndicatorImage: (docState.sortDescending ?
2547 [NSImage imageNamed:@"NSDescendingSortIndicator"] :
2548 [NSImage imageNamed:@"NSAscendingSortIndicator"])
2549 inTableColumn: tableColumn];
2550
2551 // have to reload so the rows get set up right, but a full updateStatus flashes the preview, which is annoying (and the preview won't change if we're maintaining the selection)
2552 [tableView reloadData];
2553
2554 // fix the selection
2555 [self selectPublications:pubsToSelect];
2556 [tableView scrollRowToCenter:[tableView selectedRow]]; // just go to the last one
2557
2558 // reset ourself as delegate
2559 [tableView setDelegate:self];
2560}
2561
2562- (void)saveSortOrder{
2563 // @@ if we switch to NSArrayController, we should just archive the sort descriptors (see BDSKFileContentSearchController)
2564 OFPreferenceWrapper *pw = [OFPreferenceWrapper sharedPreferenceWrapper];
2565 NSString *savedSortKey = nil( ( void * ) 0 );
2566 if ([sortKey isEqualToString:BDSKImportOrderString] || [sortKey isEqualToString:BDSKRelevanceString]) {
2567 if ([previousSortKey isEqualToString:BDSKImportOrderString] == NO( BOOL ) 0 && [previousSortKey isEqualToString:BDSKRelevanceString] == NO( BOOL ) 0)
2568 savedSortKey = previousSortKey;
2569 } else {
2570 savedSortKey = sortKey;
2571 }
2572 if (savedSortKey)
2573 [pw setObject:savedSortKey forKey:BDSKDefaultSortedTableColumnKey];
2574 [pw setBool:docState.sortDescending forKey:BDSKDefaultSortedTableColumnIsDescendingKey];
2575 [pw setObject:sortGroupsKey forKey:BDSKSortGroupsKey];
2576 [pw setBool:docState.sortGroupsDescending forKey:BDSKSortGroupsDescendingKey];
2577}
2578
2579#pragma mark -
2580#pragma mark Selection
2581
2582- (int)numberOfSelectedPubs{
2583 if ([self isDisplayingFileContentSearch])
2584 return [[fileSearchController selectedIdentifierURLs] count];
2585 else
2586 return [tableView numberOfSelectedRows];
2587}
2588
2589- (NSArray *)selectedPublications{
2590 NSArray *selPubs = nil( ( void * ) 0 );
2591 if ([self isDisplayingFileContentSearch]) {
2592 if ([[fileSearchController tableView] numberOfSelectedRows]) {
2593 NSMutableArray *tmpArray = [NSMutableArray array];
2594 NSEnumerator *itemEnum = [[fileSearchController selectedIdentifierURLs] objectEnumerator];
2595 NSURL *idURL;
2596 BibItem *pub;
2597 while (idURL = [itemEnum nextObject]) {
2598 if (pub = [publications itemForIdentifierURL:idURL])
2599 [tmpArray addObject:pub];
2600 }
2601 selPubs = tmpArray;
2602 }
2603 } else if ([tableView numberOfSelectedRows]) {
2604 selPubs = [shownPublications objectsAtIndexes:[tableView selectedRowIndexes]];
2605 }
2606 return selPubs;
2607}
2608
2609- (BOOL)selectItemsForCiteKeys:(NSArray *)citeKeys selectLibrary:(BOOL)flag {
2610
2611 // make sure we can see the publication, if it's still in the document
2612 if (flag)
2613 [self selectLibraryGroup:nil( ( void * ) 0 )];
2614 [tableView deselectAll:self];
2615 [self setSearchString:@""];
2616
2617 NSEnumerator *keyEnum = [citeKeys objectEnumerator];
2618 NSString *key;
2619 NSMutableArray *itemsToSelect = [NSMutableArray array];
2620 while (key = [keyEnum nextObject]) {
2621 BibItem *anItem = [publications itemForCiteKey:key];
2622 if (anItem)
2623 [itemsToSelect addObject:anItem];
2624 }
2625 [self selectPublications:itemsToSelect];
2626 return [itemsToSelect count];
2627}
2628
2629- (BOOL)selectItemForPartialItem:(NSDictionary *)partialItem{
2630
2631 NSString *itemKey = [partialItem objectForKey:@"net_sourceforge_bibdesk_citekey"];
2632 if(itemKey == nil( ( void * ) 0 ))
2633 itemKey = [partialItem objectForKey:BDSKCiteKeyString];
2634
2635 BOOL matchFound = NO( BOOL ) 0;
2636
2637 if(itemKey != nil( ( void * ) 0 ))
2638 matchFound = [self selectItemsForCiteKeys:[NSArray arrayWithObject:itemKey] selectLibrary:YES( BOOL ) 1];
2639
2640 return matchFound;
2641}
2642
2643- (void)selectPublication:(BibItem *)bib{
2644 [self selectPublications:[NSArray arrayWithObject:bib]];
2645}
2646
2647- (void)selectPublications:(NSArray *)bibArray{
2648
2649 NSIndexSet *indexes = [shownPublications indexesOfObjectsIdenticalTo:bibArray];
2650
2651 if([indexes count]){
2652 [tableView selectRowIndexes:indexes byExtendingSelection:NO( BOOL ) 0];
2653 [tableView scrollRowToCenter:[indexes firstIndex]];
2654 }
2655}
2656
2657- (NSArray *)selectedFileURLs {
2658 if ([self isDisplayingFileContentSearch])
2659 return [fileSearchController selectedURLs];
2660 else
2661 return [[self selectedPublications] valueForKeyPath:@"@unionOfArrays.localFiles.URL"];
2662}
2663
2664#pragma mark -
2665#pragma mark Notification handlers
2666
2667- (void)registerForNotifications{
2668 NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
2669
2670 [nc addObserver:self
2671 selector:@selector(handleGroupFieldChangedNotification:)
2672 name:BDSKGroupFieldChangedNotification
2673 object:self];
2674 [nc addObserver:self
2675 selector:@selector(handleGroupFieldAddRemoveNotification:)
2676 name:BDSKGroupFieldAddRemoveNotification
2677 object:nil( ( void * ) 0 )];
2678 [nc addObserver:self
2679 selector:@selector(handleTableSelectionChangedNotification:)
2680 name:BDSKTableSelectionChangedNotification
2681 object:self];
2682 [nc addObserver:self
2683 selector:@selector(handleGroupTableSelectionChangedNotification:)
2684 name:BDSKGroupTableSelectionChangedNotification
2685 object:self];
2686 [nc addObserver:self
2687 selector:@selector(handleBibItemChangedNotification:)
2688 name:BDSKBibItemChangedNotification
2689 object:nil( ( void * ) 0 )];
2690 [nc addObserver:self
2691 selector:@selector(handleBibItemAddDelNotification:)
2692 name:BDSKDocSetPublicationsNotification
2693 object:self];
2694 [nc addObserver:self
2695 selector:@selector(handleBibItemAddDelNotification:)
2696 name:BDSKDocAddItemNotification
2697 object:self];
2698 [nc addObserver:self
2699 selector:@selector(handleBibItemAddDelNotification:)
2700 name:BDSKDocDelItemNotification
2701 object:self];
2702 [nc addObserver:self
2703 selector:@selector(handleMacroChangedNotification:)
2704 name:BDSKMacroDefinitionChangedNotification
2705 object:nil( ( void * ) 0 )];
2706 [nc addObserver:self
2707 selector:@selector(handleFilterChangedNotification:)
2708 name:BDSKFilterChangedNotification
2709 object:nil( ( void * ) 0 )];
2710 [nc addObserver:self
2711 selector:@selector(handleGroupNameChangedNotification:)
2712 name:BDSKGroupNameChangedNotification
2713 object:nil( ( void * ) 0 )];
2714 [nc addObserver:self
2715 selector:@selector(handleStaticGroupChangedNotification:)
2716 name:BDSKStaticGroupChangedNotification
2717 object:nil( ( void * ) 0 )];
2718 [nc addObserver:self
2719 selector:@selector(handleSharedGroupUpdatedNotification:)
2720 name:BDSKSharedGroupUpdatedNotification
2721 object:nil( ( void * ) 0 )];
2722 [nc addObserver:self
2723 selector:@selector(handleSharedGroupsChangedNotification:)
2724 name:BDSKSharingClientsChangedNotification
2725 object:nil( ( void * ) 0 )];
2726 [nc addObserver:self
2727 selector:@selector(handleURLGroupUpdatedNotification:)
2728 name:BDSKURLGroupUpdatedNotification
2729 object:nil( ( void * ) 0 )];
2730 [nc addObserver:self
2731 selector:@selector(handleScriptGroupUpdatedNotification:)
2732 name:BDSKScriptGroupUpdatedNotification
2733 object:nil( ( void * ) 0 )];
2734 [nc addObserver:self
2735 selector:@selector(handleSearchGroupUpdatedNotification:)
2736 name:BDSKSearchGroupUpdatedNotification
2737 object:nil( ( void * ) 0 )];
2738 [nc addObserver:self
2739 selector:@selector(handleWebGroupUpdatedNotification:)
2740 name:BDSKWebGroupUpdatedNotification
2741 object:nil( ( void * ) 0 )];
2742 [nc addObserver:self
2743 selector:@selector(handleWillAddRemoveGroupNotification:)
2744 name:BDSKWillAddRemoveGroupNotification
2745 object:nil( ( void * ) 0 )];
2746 [nc addObserver:self
2747 selector:@selector(handleDidAddRemoveGroupNotification:)
2748 name:BDSKDidAddRemoveGroupNotification
2749 object:nil( ( void * ) 0 )];
2750 [nc addObserver:self
2751 selector:@selector(handleFlagsChangedNotification:)
2752 name:OAFlagsChangedNotification
2753 object:nil( ( void * ) 0 )];
2754 [nc addObserver:self
2755 selector:@selector(handleApplicationWillTerminateNotification:)
2756 name:NSApplicationWillTerminateNotification
2757 object:nil( ( void * ) 0 )];
2758 [nc addObserver:self
2759 selector:@selector(handleTemporaryFileMigrationNotification:)
2760 name:BDSKTemporaryFileMigrationNotification
2761 object:self];
2762 // observe this on behalf of our BibItems, or else all BibItems register for these notifications and -[BibItem dealloc] gets expensive when unregistering; this means that (shared) items without a document won't get these notifications
2763 [nc addObserver:self
2764 selector:@selector(handleCustomFieldsDidChangeNotification:)
2765 name:BDSKCustomFieldsChangedNotification
2766 object:nil( ( void * ) 0 )];
2767 // Header says NSNotificationSuspensionBehaviorCoalesce is the default if suspensionBehavior isn't specified, but at least on 10.5 it appears to be NSNotificationSuspensionBehaviorDeliverImmediately.
2768 [[NSDistributedNotificationCenter defaultCenter]
2769 addObserver:self
2770 selector:@selector(handleSkimFileDidSaveNotification:)
2771 name:@"SKSkimFileDidSaveNotification"
2772 object:nil( ( void * ) 0 )
2773 suspensionBehavior:NSNotificationSuspensionBehaviorCoalesce];
2774 [OFPreference addObserver:self
2775 selector:@selector(handleIgnoredSortTermsChangedNotification:)
2776 forPreference:[OFPreference preferenceForKey:BDSKIgnoredSortTermsKey]];
2777 [OFPreference addObserver:self
2778 selector:@selector(handleNameDisplayChangedNotification:)
2779 forPreference:[OFPreference preferenceForKey:BDSKAuthorNameDisplayKey]];
2780 [OFPreference addObserver:self
2781 selector:@selector(handleTeXPreviewNeedsUpdateNotification:)
2782 forPreference:[OFPreference preferenceForKey:BDSKBTStyleKey]];
2783 [OFPreference addObserver:self
2784 selector:@selector(handleUsesTeXChangedNotification:)
2785 forPreference:[OFPreference preferenceForKey:BDSKUsesTeXKey]];
2786}
2787
2788- (void)handleTeXPreviewNeedsUpdateNotification:(NSNotification *)notification{
2789 if([previewer isVisible])
2790 [self updatePreviews];
2791 else if([[OFPreferenceWrapper sharedPreferenceWrapper] boolForKey:BDSKUsesTeXKey] &&
2792 [[BDSKPreviewer sharedPreviewer] isWindowVisible] &&
2793 [self isMainDocument])
2794 [self updatePreviewer:[BDSKPreviewer sharedPreviewer]];
2795}
2796
2797- (void)handleUsesTeXChangedNotification:(NSNotification *)notification{
2798 [bottomPreviewButton setEnabled:[[OFPreferenceWrapper sharedPreferenceWrapper] boolForKey:BDSKUsesTeXKey] forSegment:BDSKPreviewDisplayTeX];
2799}
2800
2801- (void)handleBibItemAddDelNotification:(NSNotification *)notification{
2802 // NB: this method gets called for setPublications: also, so checking for AddItemNotification might not do what you expect
2803 BOOL isDelete = [[notification name] isEqualToString:BDSKDocDelItemNotification];
2804 if(isDelete == NO( BOOL ) 0 && [self hasLibraryGroupSelected])
2805 [self setSearchString:@""]; // clear the search when adding
2806
2807 // update smart group counts
2808 [self updateSmartGroupsCountAndContent:NO( BOOL ) 0];
2809 // this handles the remaining UI updates necessary (tableView and previews)
2810 [self updateCategoryGroupsPreservingSelection:YES( BOOL ) 1];
2811
2812 NSArray *pubs = [[notification userInfo] objectForKey:@"pubs"];
2813 [self setImported:isDelete == NO( BOOL ) 0 forPublications:pubs inGroup:nil( ( void * ) 0 )];
2814}
2815
2816- (BOOL)sortKeyDependsOnKey:(NSString *)key{
2817 if (key == nil( ( void * ) 0 ))
2818 return YES( BOOL ) 1;
2819 else if([sortKey isEqualToString:BDSKTitleString])
2820 return [key isEqualToString:BDSKTitleString] || [key isEqualToString:BDSKChapterString] || [key isEqualToString:BDSKPagesString] || [key isEqualToString:BDSKPubTypeString];
2821 else if([sortKey isEqualToString:BDSKContainerString])
2822 return [key isEqualToString:BDSKContainerString] || [key isEqualToString:BDSKJournalString] || [key isEqualToString:BDSKBooktitleString] || [key isEqualToString:BDSKVolumeString] || [key isEqualToString:BDSKSeriesString] || [key isEqualToString:BDSKPubTypeString];
2823 else if([sortKey isEqualToString:BDSKPubDateString])
2824 return [key isEqualToString:BDSKYearString] || [key isEqualToString:BDSKMonthString];
2825 else if([sortKey isEqualToString:BDSKFirstAuthorString] || [sortKey isEqualToString:BDSKSecondAuthorString] || [sortKey isEqualToString:BDSKThirdAuthorString] || [sortKey isEqualToString:BDSKLastAuthorString])
2826 return [key isEqualToString:BDSKAuthorString];
2827 else if([sortKey isEqualToString:BDSKFirstAuthorEditorString] || [sortKey isEqualToString:BDSKSecondAuthorEditorString] || [sortKey isEqualToString:BDSKThirdAuthorEditorString] || [sortKey isEqualToString:BDSKLastAuthorEditorString])
2828 return [key isEqualToString:BDSKAuthorString] || [key isEqualToString:BDSKEditorString];
2829 else
2830 return [sortKey isEqualToString:key];
2831}
2832
2833- (BOOL)searchKeyDependsOnKey:(NSString *)key{
2834 NSString *searchKey = [[searchField stringValue] isEqualToString:@""] ? nil( ( void * ) 0 ) : [searchButtonController selectedItemIdentifier];
2835 if ([searchKey isEqualToString:BDSKSkimNotesString] || [searchKey isEqualToString:BDSKFileContentSearchString])
2836 return [key isEqualToString:BDSKLocalFileString];
2837 else if (key == nil( ( void * ) 0 ))
2838 return YES( BOOL ) 1;
2839 else if ([searchKey isEqualToString:BDSKAllFieldsString])
2840 return [key isEqualToString:BDSKLocalFileString] == NO( BOOL ) 0 && [key isEqualToString:BDSKRemoteURLString] == NO( BOOL ) 0;
2841 else if ([searchKey isEqualToString:BDSKPersonString])
2842 return [key isPersonField];
2843 else
2844 return [key isEqualToString:searchKey];
2845}
2846
2847- (void)handlePrivateBibItemChanged{
2848 // we can be called from a queue after the document was closed
2849 if (docState.isDocumentClosed)
2850 return;
2851
2852 if ((docState.itemChangeMask & BDSKItemChangedFilesMask) != 0)
2853 [self updateFileViews];
2854
2855 BOOL shouldUpdateGroups = [NSString isEmptyString:[self currentGroupField]] == NO( BOOL ) 0 && (docState.itemChangeMask & BDSKItemChangedGroupFieldMask) != 0;
2856
2857 // allow updating a smart group if it's selected
2858 [self updateSmartGroupsCountAndContent:YES( BOOL ) 1];
2859
2860 if(shouldUpdateGroups){
2861 // this handles all UI updates if we call it, so don't bother with any others
2862 [self updateCategoryGroupsPreservingSelection:YES( BOOL ) 1];
2863 } else if ((docState.itemChangeMask & BDSKItemChangedSearchKeyMask) != 0) {
2864 // this handles all UI updates if we call it, so don't bother with any others
2865 [searchField sendAction:[searchField action] to:[searchField target]];
2866 } else {
2867 // groups and quicksearch won't update for us
2868 if ((docState.itemChangeMask & BDSKItemChangedSortKeyMask) != 0)
2869 [self sortPubsByKey:nil( ( void * ) 0 )];
2870 else
2871 [tableView reloadData];
2872 [self updateStatus];
2873 [self updatePreviews];
2874 }
2875
2876 docState.itemChangeMask = 0;
2877}
2878
2879// this structure is only used in the following CFSetApplierFunction
2880typedef struct __BibItemCiteKeyChangeInfo {
2881 BibItem *pub;
2882 NSCharacterSet *invalidSet;
2883 NSString *key;
2884 NSString *oldKey;
2885} _BibItemCiteKeyChangeInfo;
2886
2887static void applyChangesToCiteFieldsWithInfo(const void *citeField, void *context)
2888{
2889 NSString *field = (NSString *)citeField;
2890 _BibItemCiteKeyChangeInfo *changeInfo = context;
2891 NSString *value = [changeInfo->pub valueOfField:field inherit:NO( BOOL ) 0];
2892 // value may be nil, so check before calling rangeOfString:
2893 if (nil( ( void * ) 0 ) != value) {
2894 NSRange range = [value rangeOfString:changeInfo->oldKey];
2895 if (range.location != NSNotFound &&
2896 (range.location == 0 || [changeInfo->invalidSet characterIsMember:[value characterAtIndex:range.location]]) &&
2897 (NSMaxRange(range) == [value length] || [changeInfo->invalidSet characterIsMember:[value characterAtIndex:NSMaxRange(range)]])) {
2898 NSMutableString *tmpString = [value mutableCopy];
2899 [tmpString replaceCharactersInRange:range withString:changeInfo->key];
2900 [changeInfo->pub setField:field toValue:tmpString];
2901 [tmpString release];
2902 }
2903 }
2904}
2905
2906- (void)handleBibItemChangedNotification:(NSNotification *)notification{
2907
2908 // note: userInfo is nil if -[BibItem setFields:] is called
2909 NSDictionary *userInfo = [notification userInfo];
2910 BibItem *pub = [notification object];
2911
2912 // see if it's ours
2913 if([pub owner] != self)
2914 return;
2915
2916 NSString *changedKey = [userInfo objectForKey:@"key"];
2917 NSString *key = [pub citeKey];
2918 NSString *oldKey = nil( ( void * ) 0 );
2919 NSEnumerator *pubEnum = [publications objectEnumerator];
2920
2921 // need to handle cite keys and crossrefs if a cite key changed
2922 if([changedKey isEqualToString:BDSKCiteKeyString]){
2923 oldKey = [userInfo objectForKey:@"oldValue"];
2924 [publications changeCiteKey:oldKey toCiteKey:key forItem:pub];
2925 if([NSString isEmptyString:oldKey])
2926 oldKey = nil( ( void * ) 0 );
2927 }
2928
2929 // -[BDSKItemSearchIndexes addPublications:] will overwrite previous values for this pub
2930 if ([changedKey isIntegerField] == NO( BOOL ) 0 && [changedKey isURLField] == NO( BOOL ) 0)
2931 [searchIndexes addPublications:[NSArray arrayWithObject:pub]];
2932
2933 // access type manager outside the enumerator, since it's @synchronized...
2934 BDSKTypeManager *typeManager = [BDSKTypeManager sharedManager];
2935 NSCharacterSet *invalidSet = [typeManager invalidCharactersForField:BDSKCiteKeyString inFileType:BDSKBibtexString];
2936 NSSet *citeFields = [typeManager citationFieldsSet];
2937
2938 _BibItemCiteKeyChangeInfo changeInfo;
2939 changeInfo.invalidSet = invalidSet;
2940 changeInfo.key = key;
2941 changeInfo.oldKey = oldKey;
2942
2943 while (pub = [pubEnum nextObject]) {
2944 NSString *crossref = [pub valueOfField:BDSKCrossrefString inherit:NO( BOOL ) 0];
2945 if([NSString isEmptyString:crossref])
2946 continue;
2947
2948 // invalidate groups that depend on inherited values
2949 if ([key caseInsensitiveCompare:crossref] == NSOrderedSame)
2950 [pub invalidateGroupNames];
2951
2952 // change the crossrefs if we change the parent cite key
2953 if (oldKey) {
2954 if ([oldKey caseInsensitiveCompare:crossref] == NSOrderedSame)
2955 [pub setField:BDSKCrossrefString toValue:key];
2956 changeInfo.pub = pub;
2957
2958 // faster than creating an enumerator for what's typically a tiny set (helpful when generating keys for an entire file)
2959 CFSetApplyFunction((CFSetRef)citeFields, applyChangesToCiteFieldsWithInfo, &changeInfo);
2960 }
2961 }
2962
2963 if ([changedKey isEqualToString:[self currentGroupField]] || changedKey == nil( ( void * ) 0 ))
2964 docState.itemChangeMask |= BDSKItemChangedGroupFieldMask;
2965 if ([self sortKeyDependsOnKey:changedKey])
2966 docState.itemChangeMask |= BDSKItemChangedSortKeyMask;
2967 if ([self searchKeyDependsOnKey:changedKey])
2968 docState.itemChangeMask |= BDSKItemChangedSearchKeyMask;
2969 if ([changedKey isEqualToString:BDSKLocalFileString] || [changedKey isEqualToString:BDSKRemoteURLString])
2970 docState.itemChangeMask |= BDSKItemChangedFilesMask;
2971
2972
2973 // queue for UI updating, in case the item is changed as part of a batch process such as Find & Replace or AutoFile
2974 [self queueSelectorOnce:@selector(handlePrivateBibItemChanged)];
2975}
2976
2977- (void)handleMacroChangedNotification:(NSNotification *)aNotification{
2978 id changedOwner = [[aNotification object] owner];
2979 if(changedOwner && changedOwner != self)
2980 return; // only macro changes for ourselves or the global macros
2981
2982 [tableView reloadData];
2983 [self updatePreviews];
2984}
2985
2986- (void)handleTableSelectionChangedNotification:(NSNotification *)notification{
2987 [self updateFileViews];
2988 [self updatePreviews];
2989 [groupTableView updateHighlights];
2990}
2991
2992- (void)handleIgnoredSortTermsChangedNotification:(NSNotification *)notification{
2993 [self sortPubsByKey:nil( ( void * ) 0 )];
2994}
2995
2996- (void)handleNameDisplayChangedNotification:(NSNotification *)notification{
2997 [tableView reloadData];
2998 if([currentGroupField isPersonField])
2999 [groupTableView reloadData];
3000}
3001
3002- (void)handleFlagsChangedNotification:(NSNotification *)notification{
3003 unsigned int modifierFlags = [NSApp currentModifierFlags];
3004
3005 if (modifierFlags & NSAlternateKeyMask) {
3006 [groupAddButton setImage:[NSImage imageNamed:@"GroupAddSmart"]];
3007 [groupAddButton setAlternateImage:[NSImage imageNamed:@"GroupAddSmart_Pressed"]];
3008 [groupAddButton setToolTip:NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Add new smart group."
) value : @ "" table : ( ( void * ) 0 ) ]
(@"Add new smart group.", @"Tool tip message")];
3009 } else {
3010 [groupAddButton setImage:[NSImage imageNamed:@"GroupAdd"]];
3011 [groupAddButton setAlternateImage:[NSImage imageNamed:@"GroupAdd_Pressed"]];
3012 [groupAddButton setToolTip:NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Add new group."
) value : @ "" table : ( ( void * ) 0 ) ]
(@"Add new group.", @"Tool tip message")];
3013 }
3014}
3015
3016- (void)handleApplicationWillTerminateNotification:(NSNotification *)notification{
3017 [self saveSortOrder];
3018}
3019
3020- (void)handleCustomFieldsDidChangeNotification:(NSNotification *)notification{
3021 [publications makeObjectsPerformSelector:@selector(customFieldsDidChange:) withObject:notification];
3022 [tableView setupTableColumnsWithIdentifiers:[tableView tableColumnIdentifiers]];
3023 // current group field may have changed its type (string->person)
3024 [self updateSmartGroupsCountAndContent:YES( BOOL ) 1];
3025 [self updateCategoryGroupsPreservingSelection:YES( BOOL ) 1];
3026 [self updatePreviews];
3027}
3028
3029- (void)handleTemporaryFileMigrationNotification:(NSNotification *)notification{
3030 // display after the window loads so we can use a sheet, and the migration controller window is in front
3031 if ([[NSUserDefaults standardUserDefaults] boolForKey:@"BDSKDisableMigrationWarning"] == NO( BOOL ) 0)
3032 docState.displayMigrationAlert = YES( BOOL ) 1;
3033}
3034
3035- (void)handleSkimFileDidSaveNotification:(NSNotification *)notification{
3036 NSString *path = [notification object];
3037 NSEnumerator *pubEnum = [publications objectEnumerator];
3038 BibItem *pub;
3039 NSDictionary *notifInfo = [NSDictionary dictionaryWithObjectsAndKeys:BDSKLocalFileString, @"key", nil( ( void * ) 0 )];
3040
3041 while (pub = [pubEnum nextObject]) {
3042 if ([[[pub existingLocalFiles] valueForKey:@"path"] containsObject:path])
3043 [[NSNotificationCenter defaultCenter] postNotificationName:BDSKBibItemChangedNotification object:pub userInfo:notifInfo];
3044 }
3045}
3046
3047- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
3048 if (context == BDSKDocumentObservationContext) {
3049 if (object == sideFileView) {
3050 float iconScale = [sideFileView autoScales] ? 0.0 : [sideFileView iconScale];
3051 [[OFPreferenceWrapper sharedPreferenceWrapper] setFloat:iconScale forKey:BDSKSideFileViewIconScaleKey];
3052 } else if (object == bottomFileView) {
3053 float iconScale = [bottomFileView autoScales] ? 0.0 : [bottomFileView iconScale];
3054 [[OFPreferenceWrapper sharedPreferenceWrapper] setFloat:iconScale forKey:BDSKBottomFileViewIconScaleKey];
3055 }
3056 } else {
3057 [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
3058 }
3059}
3060
3061#pragma mark -
3062#pragma mark Preview updating
3063
3064- (void)doUpdatePreviews{
3065 // we can be called from a queue after the document was closed
3066 if (docState.isDocumentClosed)
3067 return;
3068
3069 OBASSERTdo { if ( ! ( [ NSThread inMainThread ] ) ) OBAssertFailed ( "ASSERT"
, "[NSThread inMainThread]" , "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/BibDocument.m"
, 3069 ) ; } while ( ( BOOL ) 0 )
([NSThread inMainThread]);
3070
3071 //take care of the preview field (NSTextView below the pub table); if the enumerator is nil, the view will get cleared out
3072 [self updateBottomPreviewPane];
3073 [self updateSidePreviewPane];
3074
3075 if([[OFPreferenceWrapper sharedPreferenceWrapper] boolForKey:BDSKUsesTeXKey] &&
3076 [[BDSKPreviewer sharedPreviewer] isWindowVisible] &&
3077 [self isMainDocument])
3078 [self updatePreviewer:[BDSKPreviewer sharedPreviewer]];
3079}
3080
3081- (void)updatePreviews{
3082 // Coalesce these messages here, since something like select all -> generate cite keys will force a preview update for every
3083 // changed key, so we have to update all the previews each time. This should be safer than using cancelPrevious... since those
3084 // don't get performed on the main thread (apparently), and can lead to problems.
3085 if (docState.isDocumentClosed == NO( BOOL ) 0)
3086 [self queueSelectorOnce:@selector(doUpdatePreviews)];
3087}
3088
3089- (void)updatePreviewer:(BDSKPreviewer *)aPreviewer{
3090 NSArray *items = [self selectedPublications];
3091 NSString *bibString = [items count] ? [self previewBibTeXStringForPublications:items] : nil( ( void * ) 0 );
3092 [aPreviewer updateWithBibTeXString:bibString citeKeys:[items valueForKey:@"citeKey"]];
3093}
3094
3095- (void)displayTemplatedPreview:(NSString *)templateStyle inTextView:(NSTextView *)textView{
3096
3097 if([textView isHidden] || NSIsEmptyRect([textView visibleRect]))
3098 return;
3099
3100 NSArray *items = [self selectedPublications];
3101 unsigned int maxItems = [[OFPreferenceWrapper sharedPreferenceWrapper] integerForKey:BDSKPreviewMaxNumberKey];
3102
3103 if (maxItems > 0 && [items count] > maxItems)
3104 items = [items subarrayWithRange:NSMakeRange(0, maxItems)];
3105
3106 // do this _before_ messing with the text storage; otherwise you can have a leftover selection that ends up being out of range
3107 static NSArray *zeroRanges = nil( ( void * ) 0 );
3108 if (zeroRanges == nil( ( void * ) 0 )) zeroRanges = [[NSArray alloc] initWithObjects:[NSValue valueWithRange: NSMakeRange(0, 0)], nil( ( void * ) 0 )];
3109
3110 NSTextStorage *textStorage = [textView textStorage];
3111 [textView setSelectedRanges:zeroRanges];
3112
3113 NSLayoutManager *layoutManager = [[textStorage layoutManagers] lastObject];
3114 [layoutManager retain];
3115 [textStorage removeLayoutManager:layoutManager]; // optimization: make sure the layout manager doesn't do any work while we're loading
3116
3117 [textStorage beginEditing];
3118
3119 BDSKTemplate *template = [BDSKTemplate templateForStyle:templateStyle];
3120 if (template == nil( ( void * ) 0 ))
3121 template = [BDSKTemplate templateForStyle:[BDSKTemplate defaultStyleNameForFileType:@"rtf"]];
3122
3123 // make sure this is really one of the attributed string types...
3124 if([template templateFormat] & BDSKRichTextTemplateFormat){
3125 NSAttributedString *templateString = [BDSKTemplateObjectProxy attributedStringByParsingTemplate:template withObject:self publications:items documentAttributes:NULL( ( void * ) 0 )];
3126 [textStorage setAttributedString:templateString];
3127 } else if([template templateFormat] & BDSKPlainTextTemplateFormat){
3128 // parse as plain text, so the HTML is interpreted properly by NSAttributedString
3129 NSString *str = [BDSKTemplateObjectProxy stringByParsingTemplate:template withObject:self publications:items];
3130 // we generally assume UTF-8 encoding for all template-related files
3131 NSAttributedString *templateString = nil( ( void * ) 0 );
3132 if ([template templateFormat] == BDSKPlainHTMLTemplateFormat)
3133 templateString = [[NSAttributedString alloc] initWithHTML:[str dataUsingEncoding:NSUTF8StringEncoding] documentAttributes:NULL( ( void * ) 0 )];
3134 else
3135 templateString = [[NSAttributedString alloc] initWithString:str attributes:[NSDictionary dictionaryWithObjectsAndKeys:[NSFont systemFontOfSize:0.0], NSFontAttributeName, nil( ( void * ) 0 )]];
3136 [textStorage setAttributedString:templateString];
3137 [templateString release];
3138 } else {
3139 [[textStorage mutableString] setString:@""];
3140 }
3141
3142 [textStorage endEditing];
3143 [textStorage addLayoutManager:layoutManager];
3144 [layoutManager release];
3145
3146 if([NSString isEmptyString:[searchField stringValue]] == NO( BOOL ) 0)
3147 [textView highlightComponentsOfSearchString:[searchField stringValue]];
3148}
3149
3150- (void)prepareForTeXPreview {
3151 if(previewer == nil( ( void * ) 0 ) && [[OFPreferenceWrapper sharedPreferenceWrapper] boolForKey:BDSKUsesTeXKey]){
3152 previewer = [[BDSKPreviewer alloc] init];
3153 NSDictionary *xatrrDefaults = [self mainWindowSetupDictionaryFromExtendedAttributes];
3154 [previewer setPDFScaleFactor:[xatrrDefaults floatForKey:BDSKPreviewPDFScaleFactorKey defaultValue:0.0]];
3155 [previewer setRTFScaleFactor:[xatrrDefaults floatForKey:BDSKPreviewRTFScaleFactorKey defaultValue:1.0]];
3156 BDSKEdgeView *previewerBox = [[[BDSKEdgeView alloc] init] autorelease];
3157 [previewerBox setEdges:BDSKEveryEdgeMask];
3158 [previewerBox setColor:[NSColor lightGrayColor] forEdge:NSMaxYEdge];
3159 [previewerBox setContentView:[previewer pdfView]];
3160 [[bottomPreviewTabView tabViewItemAtIndex:BDSKPreviewDisplayTeX] setView:previewerBox];
3161 }
3162
3163 [[previewer progressOverlay] overlayView:bottomPreviewTabView];
3164}
3165
3166- (void)cleanupAfterTeXPreview {
3167 [[previewer progressOverlay] remove];
3168 [previewer updateWithBibTeXString:nil( ( void * ) 0 )];
3169}
3170
3171- (void)updateBottomPreviewPane{
3172 int tabIndex = [bottomPreviewTabView indexOfTabViewItem:[bottomPreviewTabView selectedTabViewItem]];
3173 if (bottomPreviewDisplay != tabIndex) {
3174 if (bottomPreviewDisplay == BDSKPreviewDisplayTeX)
3175 [self prepareForTeXPreview];
3176 else if (tabIndex == BDSKPreviewDisplayTeX)
3177 [self cleanupAfterTeXPreview];
3178 [bottomPreviewTabView selectTabViewItemAtIndex:bottomPreviewDisplay];
3179 }
3180
3181 if (bottomPreviewDisplay == BDSKPreviewDisplayTeX)
3182 [self updatePreviewer:previewer];
3183 else if (bottomPreviewDisplay == BDSKPreviewDisplayFiles)
3184 [bottomFileView reloadIcons];
3185 else
3186 [self displayTemplatedPreview:bottomPreviewDisplayTemplate inTextView:bottomPreviewTextView];
3187}
3188
3189- (void)updateSidePreviewPane{
3190 int tabIndex = [sidePreviewTabView indexOfTabViewItem:[sidePreviewTabView selectedTabViewItem]];
3191 if (sidePreviewDisplay != tabIndex) {
3192 [sidePreviewTabView selectTabViewItemAtIndex:sidePreviewDisplay];
3193 }
3194
3195 if (sidePreviewDisplay == BDSKPreviewDisplayFiles)
3196 [sideFileView reloadIcons];
3197 else
3198 [self displayTemplatedPreview:sidePreviewDisplayTemplate inTextView:sidePreviewTextView];
3199}
3200
3201#pragma mark FileView
3202
3203typedef struct _fileViewObjectContext {
3204 CFMutableArrayRef array;
3205 NSString *title;
3206} fileViewObjectContext;
3207
3208static void addFileViewObjectForURLToArray(const void *value, void *context)
3209{
3210 fileViewObjectContext *ctxt = context;
3211 // value is BDSKLinkedFile *
3212 BDSKFileViewObject *obj = [[BDSKFileViewObject alloc] initWithURL:[(BDSKLinkedFile *)value displayURL] string:ctxt->title];
3213 CFArrayAppendValue(ctxt->array, obj);
3214 [obj release];
3215}
3216
3217static void addAllFileViewObjectsForItemToArray(const void *value, void *context)
3218{
3219 CFArrayRef allURLs = (CFArrayRef)[(BibItem *)value files];
3220 if (CFArrayGetCount(allURLs)) {
3221 fileViewObjectContext ctxt;
3222 ctxt.array = context;
3223 ctxt.title = [(BibItem *)value displayTitle];
3224 CFArrayApplyFunction(allURLs, CFRangeMake(0, CFArrayGetCount(allURLs)), addFileViewObjectForURLToArray, &ctxt);
3225 }
3226}
3227
3228- (NSArray *)shownFiles {
3229 if (shownFiles == nil( ( void * ) 0 )) {
3230 if ([self isDisplayingFileContentSearch]) {
3231 shownFiles = [[fileSearchController selectedResults] mutableCopy];
3232 } else {
3233 NSArray *selPubs = [self selectedPublications];
3234 if (selPubs) {
3235 shownFiles = [[NSMutableArray alloc] initWithCapacity:[selPubs count]];
3236 CFArrayApplyFunction((CFArrayRef)selPubs, CFRangeMake(0, [selPubs count]), addAllFileViewObjectsForItemToArray, shownFiles);
3237 }
3238 }
3239 }
3240 return shownFiles;
3241}
3242
3243- (void)updateFileViews {
3244 [shownFiles release];
3245 shownFiles = nil( ( void * ) 0 );
3246
3247 [sideFileView reloadIcons];
3248 [bottomFileView reloadIcons];
3249}
3250
3251- (NSString *)fileView:(FileView *)aFileView subtitleAtIndex:(NSUInteger)anIndex;
3252{
3253 return [[[self shownFiles] objectAtIndex:anIndex] valueForKey:@"string"];
3254}
3255
3256- (NSUInteger)numberOfURLsInFileView:(FileView *)aFileView {
3257 return [[self shownFiles] count];
3258}
3259
3260- (NSURL *)fileView:(FileView *)aFileView URLAtIndex:(NSUInteger)anIndex {
3261 return [[[self shownFiles] objectAtIndex:anIndex] valueForKey:@"URL"];
3262}
3263
3264- (BOOL)fileView:(FileView *)aFileView shouldOpenURL:(NSURL *)aURL {
3265 if ([aURL isFileURL]) {
3266 NSString *searchString = @"";
3267 // See bug #1344720; don't search if this is a known field (Title, Author, etc.). This feature can be annoying because Preview.app zooms in on the search result in this case, in spite of your zoom settings (bug report filed with Apple).
3268 if([[searchButtonController selectedItemIdentifier] isEqualToString:BDSKFileContentSearchString])
3269 searchString = [searchField stringValue];
3270 return [[NSWorkspace sharedWorkspace] openURL:aURL withSearchString:searchString] == NO( BOOL ) 0;
3271 } else {
3272 return [[NSWorkspace sharedWorkspace] openLinkedURL:aURL] == NO( BOOL ) 0;
3273 }
3274}
3275
3276- (void)fileView:(FileView *)aFileView willPopUpMenu:(NSMenu *)menu onIconAtIndex:(NSUInteger)anIndex {
3277 NSURL *theURL = anIndex == NSNotFound ? nil( ( void * ) 0 ) : [[[self shownFiles] objectAtIndex:anIndex] valueForKey:@"URL"];
3278 int i;
3279 NSMenuItem *item;
3280
3281 if (theURL && [[aFileView selectionIndexes] count] <= 1) {
3282 i = [menu indexOfItemWithTag:FVOpenMenuItemTag];
3283 [menu insertItemWithTitle:[NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Open With"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"Open With", @"Menu item title") stringByAppendingEllipsis]
Value stored to 'i' is never read
3284 andSubmenuOfApplicationsForURL:theURL atIndex:++i];
3285
3286 if ([theURL isFileURL]) {
3287 i = [menu indexOfItemWithTag:FVRevealMenuItemTag];
3288 item = [menu insertItemWithTitle:[NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Skim Notes"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"Skim Notes",@"Menu item title: Skim Note...") stringByAppendingEllipsis]
3289 action:@selector(showNotesForLinkedFile:)
3290 keyEquivalent:@""
3291 atIndex:++i];
3292 [item setRepresentedObject:theURL];
3293
3294 item = [menu insertItemWithTitle:[NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Copy Skim Notes"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"Copy Skim Notes",@"Menu item title: Copy Skim Notes...") stringByAppendingEllipsis]
3295 action:@selector(copyNotesForLinkedFile:)
3296 keyEquivalent:@""
3297 atIndex:++i];
3298 [item setRepresentedObject:theURL];
3299 }
3300 }
3301}
3302
3303#pragma mark -
3304#pragma mark Status bar
3305
3306- (void)setStatus:(NSString *)status {
3307 [self setStatus:status immediate:YES( BOOL ) 1];
3308}
3309
3310- (void)setStatus:(NSString *)status immediate:(BOOL)now {
3311 if(now)
3312 [statusBar setStringValue:status];
3313 else
3314 [statusBar performSelector:@selector(setStringValue:) withObject:status afterDelay:0.01];
3315}
3316
3317- (void)updateStatus{
3318 NSMutableString *statusStr = [[NSMutableString alloc] init];
3319 NSString *ofStr = NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "of" ) value
: @ "" table : ( ( void * ) 0 ) ]
(@"of", @"partial status message: [number] of [number] publications");
3320
3321 if ([self isDisplayingFileContentSearch]) {
3322
3323 int shownItemsCount = [[fileSearchController filteredResults] count];
3324 int totalItemsCount = [[fileSearchController results] count];
3325
3326 [statusStr appendFormat:@"%i %@", shownItemsCount, (shownItemsCount == 1) ? NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "item" )
value : @ "" table : ( ( void * ) 0 ) ]
(@"item", @"item, in status message") : NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "items"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"items", @"items, in status message")];
3327
3328 if (shownItemsCount != totalItemsCount) {
3329 NSString *groupStr = ([groupTableView numberOfSelectedRows] == 1) ?
3330 [NSString stringWithFormat:@"%@ \"%@\"", NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "in group"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"in group", @"Partial status message"), [[[self selectedGroups] lastObject] stringValue]] :
3331 NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "in multiple groups"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"in multiple groups", @"Partial status message");
3332 [statusStr appendFormat:@" %@ (%@ %i)", groupStr, ofStr, totalItemsCount];
3333 }
3334
3335 } else {
3336
3337 int shownPubsCount = [shownPublications count];
3338 int groupPubsCount = [groupedPublications count];
3339 int totalPubsCount = [publications count];
3340
3341 if (shownPubsCount != groupPubsCount) {
3342 [statusStr appendFormat:@"%i %@ ", shownPubsCount, ofStr];
3343 }
3344 [statusStr appendFormat:@"%i %@", groupPubsCount, (groupPubsCount == 1) ? NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "publication"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"publication", @"publication, in status message") : NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "publications"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"publications", @"publications, in status message")];
3345 // we can have only a single external group selected at a time
3346 if ([self hasWebGroupSelected] == YES( BOOL ) 1) {
3347 [statusStr appendFormat:@" %@", NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "in web group"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"in web group", @"Partial status message")];
3348 } else if ([self hasSharedGroupsSelected] == YES( BOOL ) 1) {
3349 [statusStr appendFormat:@" %@ \"%@\"", NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "in shared group"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"in shared group", @"Partial status message"), [[[self selectedGroups] lastObject] stringValue]];
3350 } else if ([self hasURLGroupsSelected] == YES( BOOL ) 1) {
3351 [statusStr appendFormat:@" %@ \"%@\"", NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "in external file group"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"in external file group", @"Partial status message"), [[[self selectedGroups] lastObject] stringValue]];
3352 } else if ([self hasScriptGroupsSelected] == YES( BOOL ) 1) {
3353 [statusStr appendFormat:@" %@ \"%@\"", NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "in script group"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"in script group", @"Partial status message"), [[[self selectedGroups] lastObject] stringValue]];
3354 } else if ([self hasSearchGroupsSelected] == YES( BOOL ) 1) {
3355 BDSKSearchGroup *group = [[self selectedGroups] firstObject];
3356 [statusStr appendFormat:NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ " in \"%@\" search group"
) value : @ "" table : ( ( void * ) 0 ) ]
(@" in \"%@\" search group", @"Partial status message"), [[group serverInfo] name]];
3357 int matchCount = [group numberOfAvailableResults];
3358 if (matchCount == 1)
3359 [statusStr appendFormat:NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ ". There was 1 match."
) value : @ "" table : ( ( void * ) 0 ) ]
(@". There was 1 match.", @"Partial status message")];
3360 else if (matchCount > 1)
3361 [statusStr appendFormat:NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ ". There were %i matches."
) value : @ "" table : ( ( void * ) 0 ) ]
(@". There were %i matches.", @"Partial status message"), matchCount];
3362 if ([group hasMoreResults])
3363 [statusStr appendString:NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ " Hit \"Search\" to load more."
) value : @ "" table : ( ( void * ) 0 ) ]
(@" Hit \"Search\" to load more.", @"Partial status message")];
3364 else if (groupPubsCount < matchCount)
3365 [statusStr appendString:NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ " Some results could not be parsed."
) value : @ "" table : ( ( void * ) 0 ) ]
(@" Some results could not be parsed.", @"Partial status message")];
3366 } else if (groupPubsCount != totalPubsCount) {
3367 NSString *groupStr = ([groupTableView numberOfSelectedRows] == 1) ?
3368 [NSString stringWithFormat:@"%@ \"%@\"", NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "in group"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"in group", @"Partial status message"), [[[self selectedGroups] lastObject] stringValue]] :
3369 NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "in multiple groups"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"in multiple groups", @"Partial status message");
3370 [statusStr appendFormat:@" %@ (%@ %i)", groupStr, ofStr, totalPubsCount];
3371 }
3372
3373 }
3374
3375 [self setStatus:statusStr];
3376 [statusStr release];
3377}
3378
3379#pragma mark -
3380#pragma mark Control view animation
3381
3382- (BOOL)isDisplayingSearchButtons { return [documentWindow isEqual:[[searchButtonController view] window]]; }
3383- (BOOL)isDisplayingFileContentSearch { return [documentWindow isEqual:[[fileSearchController tableView] window]]; }
3384- (BOOL)isDisplayingSearchGroupView { return [documentWindow isEqual:[[searchGroupViewController view] window]]; }
3385- (BOOL)isDisplayingWebGroupView { return [documentWindow isEqual:[[webGroupViewController view] window]]; }
3386
3387- (void)insertControlView:(NSView *)controlView atTop:(BOOL)insertAtTop {
3388 if ([documentWindow isEqual:[controlView window]])
3389 return;
3390
3391 NSArray *views = [[[mainBox contentView] subviews] copy];
3392 NSEnumerator *viewEnum;
3393 NSView *view;
3394 NSRect controlFrame = [controlView frame];
3395 NSRect startRect, endRect = [splitView frame];
3396
3397 if (insertAtTop) {
3398 viewEnum = [views objectEnumerator];
3399 while (view = [viewEnum nextObject])
3400 endRect = NSUnionRect(endRect, [view frame]);
3401 }
3402 startRect = endRect;
3403 startRect.size.height += NSHeight(controlFrame);
3404 controlFrame.size.width = NSWidth(endRect);
3405 controlFrame.origin.x = NSMinX(endRect);
3406 controlFrame.origin.y = NSMaxY(endRect);
3407 [controlView setFrame:controlFrame];
3408
3409 NSView *clipView = [[[NSView alloc] initWithFrame:endRect] autorelease];
3410 NSView *resizeView = [[[NSView alloc] initWithFrame:startRect] autorelease];
3411
3412 [mainBox addSubview:clipView];
3413 [clipView addSubview:resizeView];
3414 if (insertAtTop) {
3415 viewEnum = [views objectEnumerator];
3416 while (view = [viewEnum nextObject])
3417 [resizeView addSubview:view];
3418 } else {
3419 [resizeView addSubview:splitView];
3420 }
3421 [resizeView addSubview:controlView];
3422 [views release];
3423
3424 [NSViewAnimation animateResizeView:resizeView toRect:endRect];
3425
3426 views = [[resizeView subviews] copy];
3427 viewEnum = [views objectEnumerator];
3428 while (view = [viewEnum nextObject])
3429 [mainBox addSubview:view];
3430 [clipView removeFromSuperview];
3431
3432 [views release];
3433
3434 [mainBox setNeedsDisplay:YES( BOOL ) 1];
3435 [documentWindow displayIfNeeded];
3436}
3437
3438- (void)removeControlView:(NSView *)controlView {
3439 if ([documentWindow isEqual:[controlView window]] == NO( BOOL ) 0)
3440 return;
3441
3442 NSArray *views = [[NSArray alloc] initWithArray:[[mainBox contentView] subviews] copyItems:NO( BOOL ) 0];
3443 NSRect controlFrame = [controlView frame];
3444 NSRect endRect, startRect = NSUnionRect([splitView frame], controlFrame);
3445
3446 endRect = startRect;
3447 endRect.size.height += NSHeight(controlFrame);
3448
3449 NSView *clipView = [[[NSView alloc] initWithFrame:startRect] autorelease];
3450 NSView *resizeView = [[[NSView alloc] initWithFrame:startRect] autorelease];
3451
3452 /* Retaining the graphics context is a workaround for our bug #1714565.
3453
3454 To reproduce:
3455 1) search LoC for "Bob Dylan"
3456 2) enter "ab" in the document's searchfield
3457 3) click the "Import" button for any one of the items
3458 4) crash when trying to retain a dealloced instance of NSWindowGraphicsContext (enable zombies) in [resizeView addSubview:]
3459
3460 This seems to be an AppKit focus stack bug. Something still isn't quite correct, since the button for -[BDSKMainTableView importItem:] is in the wrong table column momentarily, but I think that's unrelated to the crasher.
3461 */
3462 [[[NSGraphicsContext currentContext] retain] autorelease];
3463
3464 [mainBox addSubview:clipView];
3465 [clipView addSubview:resizeView];
3466 NSEnumerator *viewEnum = [views objectEnumerator];
3467 NSView *view;
3468
3469 while (view = [viewEnum nextObject]) {
3470 if (NSContainsRect(startRect, [view frame]))
3471 [resizeView addSubview:view];
3472 }
3473 [resizeView addSubview:controlView];
3474 [views release];
3475
3476 [NSViewAnimation animateResizeView:resizeView toRect:endRect];
3477
3478 [controlView removeFromSuperview];
3479 views = [[resizeView subviews] copy];
3480 viewEnum = [views objectEnumerator];
3481 while (view = [viewEnum nextObject])
3482 [mainBox addSubview:view];
3483 [clipView removeFromSuperview];
3484
3485 [views release];
3486
3487 [mainBox setNeedsDisplay:YES( BOOL ) 1];
3488 [documentWindow displayIfNeeded];
3489}
3490
3491#pragma mark -
3492#pragma mark Columns Menu
3493
3494- (NSMenu *)columnsMenu{
3495 return [tableView columnsMenu];
3496}
3497
3498- (NSMenu *)menuForImagePopUpButton:(BDSKImagePopUpButton *)view{
3499 NSMenu *menu = actionMenu;
3500 NSMenu *submenu = nil( ( void * ) 0 );
3501 int i, count = [menu numberOfItems];
3502
3503 for (i = 0; submenu == nil( ( void * ) 0 ) && i < count; i++)
3504 submenu = [[menu itemAtIndex:i] submenu];
3505 if (submenu) {
3506 while ([submenu numberOfItems])
3507 [submenu removeItemAtIndex:0];
3508 NSArray *styles = [BDSKTemplate allStyleNames];
3509 count = [styles count];
3510 for (i = 0; i < count; i++) {
3511 NSMenuItem *item = [submenu addItemWithTitle:[styles objectAtIndex:i] action:@selector(copyAsAction:) keyEquivalent:@""];
3512 [item setTarget:self];
3513 [item setTag:BDSKTemplateDragCopyType + i];
3514 }
3515 }
3516 return menu;
3517}
3518
3519#pragma mark Template Menu
3520
3521- (void)menuNeedsUpdate:(NSMenu *)menu {
3522 if (menu == bottomTemplatePreviewMenu || menu == sideTemplatePreviewMenu) {
3523 NSMutableArray *styles = [NSMutableArray arrayWithArray:[BDSKTemplate allStyleNamesForFileType:@"rtf"]];
3524 [styles addObjectsFromArray:[BDSKTemplate allStyleNamesForFileType:@"rtfd"]];
3525 [styles addObjectsFromArray:[BDSKTemplate allStyleNamesForFileType:@"doc"]];
3526 [styles addObjectsFromArray:[BDSKTemplate allStyleNamesForFileType:@"html"]];
3527
3528 while ([menu numberOfItems])
3529 [menu removeItemAtIndex:0];
3530
3531 NSEnumerator *styleEnum = [styles objectEnumerator];
3532 NSString *style;
3533 NSMenuItem *item;
3534 SEL action = menu == bottomTemplatePreviewMenu ? @selector(changePreviewDisplay:) : @selector(changeSidePreviewDisplay:);
3535
3536 while (style = [styleEnum nextObject]) {
3537 item = [menu addItemWithTitle:style action:action keyEquivalent:@""];
3538 [item setTarget:self];
3539 [item setTag:BDSKPreviewDisplayText];
3540 [item setRepresentedObject:style];
3541 }
3542 }
3543}
3544
3545#pragma mark -
3546#pragma mark Printing support
3547
3548- (IBActionvoid)printDocument:(id)sender{
3549 if (bottomPreviewDisplay == BDSKPreviewDisplayTeX)
3550 [[previewer pdfView] printWithInfo:[self printInfo] autoRotate:YES( BOOL ) 1];
3551 else
3552 [super printDocument:sender];
3553}
3554
3555- (NSView *)printableView{
3556 BDSKPrintableView *printableView = [[[BDSKPrintableView alloc] initForScreenDisplay:NO( BOOL ) 0] autorelease];
3557 NSAttributedString *attrString = nil( ( void * ) 0 );
3558 NSString *string = nil( ( void * ) 0 );
3559 if (bottomPreviewDisplay == BDSKPreviewDisplayText) {
3560 attrString = [bottomPreviewTextView textStorage];
3561 } else if (sidePreviewDisplay == BDSKPreviewDisplayText) {
3562 attrString = [sidePreviewTextView textStorage];
3563 } else {
3564 // this occurs only when both FileViews are displayed, probably never happens
3565 string = [self bibTeXStringForPublications:[self selectedPublications]];
3566 }
3567 if (attrString)
3568 [printableView setAttributedString:attrString];
3569 else if (string)
3570 [printableView setString:string];
3571 else
3572 [printableView setString:NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Error: nothing to print from document preview"
) value : @ "" table : ( ( void * ) 0 ) ]
(@"Error: nothing to print from document preview", @"printing error")];
3573 return printableView;
3574}
3575
3576- (NSPrintOperation *)printOperationWithSettings:(NSDictionary *)printSettings error:(NSError **)outError {
3577 NSPrintInfo *info = [[self printInfo] copy];
3578 [[info dictionary] addEntriesFromDictionary:printSettings];
3579 NSPrintOperation *printOperation = [NSPrintOperation printOperationWithView:[self printableView] printInfo:info];
3580 [info release];
3581 NSPrintPanel *printPanel = [printOperation printPanel];
3582 if ([printPanel respondsToSelector:@selector(setOptions:)])
3583 [printPanel setOptions:NSPrintPanelShowsCopies | NSPrintPanelShowsPageRange | NSPrintPanelShowsPaperSize | NSPrintPanelShowsOrientation | NSPrintPanelShowsScaling | NSPrintPanelShowsPreview];
3584 return printOperation;
3585}
3586
3587#pragma mark SplitView delegate
3588
3589- (void)splitView:(NSSplitView *)sender resizeSubviewsWithOldSize:(NSSize)oldSize {
3590 int i = [[sender subviews] count] - 2;
3591 OBASSERTdo { if ( ! ( i >= 0 ) ) OBAssertFailed ( "ASSERT" , "i >= 0"
, "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/BibDocument.m"
, 3591 ) ; } while ( ( BOOL ) 0 )
(i >= 0);
3592 NSView *zerothView = i == 0 ? nil( ( void * ) 0 ) : [[sender subviews] objectAtIndex:0];
3593 NSView *firstView = [[sender subviews] objectAtIndex:i];
3594 NSView *secondView = [[sender subviews] objectAtIndex:++i];
3595 NSRect zerothFrame = zerothView ? [zerothView frame] : NSZeroRect;
3596 NSRect firstFrame = [firstView frame];
3597 NSRect secondFrame = [secondView frame];
3598
3599 if (sender == splitView) {
3600 // first = table, second = preview, zeroth = web
3601 float contentHeight = NSHeight([sender frame]) - i * [sender dividerThickness];
3602 float factor = contentHeight / (oldSize.height - i * [sender dividerThickness]);
3603 secondFrame = NSIntegralRect(secondFrame);
3604 zerothFrame.size.height = floorf(factor * NSHeight(zerothFrame));
3605 firstFrame.size.height = floorf(factor * NSHeight(firstFrame));
3606 secondFrame.size.height = floorf(factor * NSHeight(secondFrame));
3607 if (NSHeight(zerothFrame) < 1.0)
3608 zerothFrame.size.height = 0.0;
3609 if (NSHeight(firstFrame) < 1.0)
3610 firstFrame.size.height = 0.0;
3611 if (NSHeight(secondFrame) < 1.0)
3612 secondFrame.size.height = 0.0;
3613 // randomly divide the remaining gap over the two views; NSSplitView dumps it all over the last view, which grows that one more than the others
3614 int gap = (int)(contentHeight - NSHeight(zerothFrame) - NSHeight(firstFrame) - NSHeight(secondFrame));
3615 while (gap > 0) {
3616 i = floorf((3.0f * rand()) / RAND_MAX0x7fffffff);
3617 if (i == 0 && NSHeight(zerothFrame) > 0.0) {
3618 zerothFrame.size.height += 1.0;
3619 gap--;
3620 } else if (i == 1 && NSHeight(firstFrame) > 0.0) {
3621 firstFrame.size.height += 1.0;
3622 gap--;
3623 } else if (i == 2 && NSHeight(secondFrame) > 0.0) {
3624 secondFrame.size.height += 1.0;
3625 gap--;
3626 }
3627 }
3628 zerothFrame.size.width = firstFrame.size.width = secondFrame.size.width = NSWidth([sender frame]);
3629 if (zerothView)
3630 firstFrame.origin.y = NSMaxY(zerothFrame) + [sender dividerThickness];
3631 secondFrame.origin.y = NSMaxY(firstFrame) + [sender dividerThickness];
3632 } else {
3633 // zeroth = group, first = table+preview, second = fileview
3634 float contentWidth = NSWidth([sender frame]) - 2 * [sender dividerThickness];
3635 if (NSWidth(zerothFrame) < 1.0)
3636 zerothFrame.size.width = 0.0;
3637 if (NSWidth(secondFrame) < 1.0)
3638 secondFrame.size.width = 0.0;
3639 if (contentWidth < NSWidth(zerothFrame) + NSWidth(secondFrame)) {
3640 float factor = contentWidth / (oldSize.width - [sender dividerThickness]);
3641 zerothFrame.size.width = floorf(factor * NSWidth(zerothFrame));
3642 secondFrame.size.width = floorf(factor * NSWidth(secondFrame));
3643 }
3644 firstFrame.size.width = contentWidth - NSWidth(zerothFrame) - NSWidth(secondFrame);
3645 firstFrame.origin.x = NSMaxX(zerothFrame) + [sender dividerThickness];
3646 secondFrame.origin.x = NSMaxX(firstFrame) + [sender dividerThickness];
3647 zerothFrame.size.height = firstFrame.size.height = secondFrame.size.height = NSHeight([sender frame]);
3648 }
3649
3650 [zerothView setFrame:zerothFrame];
3651 [firstView setFrame:firstFrame];
3652 [secondView setFrame:secondFrame];
3653 [sender adjustSubviews];
3654}
3655
3656- (void)splitView:(BDSKSplitView *)sender doubleClickedDividerAt:(int)offset {
3657 int i = [[sender subviews] count] - 2;
3658 OBASSERTdo { if ( ! ( i >= 0 ) ) OBAssertFailed ( "ASSERT" , "i >= 0"
, "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/BibDocument.m"
, 3658 ) ; } while ( ( BOOL ) 0 )
(i >= 0);
3659 NSView *zerothView = i == 0 ? nil( ( void * ) 0 ) : [[sender subviews] objectAtIndex:0];
3660 NSView *firstView = [[sender subviews] objectAtIndex:i];
3661 NSView *secondView = [[sender subviews] objectAtIndex:++i];
3662 NSRect zerothFrame = zerothView ? [zerothView frame] : NSZeroRect;
3663 NSRect firstFrame = [firstView frame];
3664 NSRect secondFrame = [secondView frame];
3665
3666 if (sender == splitView && offset == i - 1) {
3667 // first = table, second = preview, zeroth = web
3668 if(NSHeight(secondFrame) > 0){ // can't use isSubviewCollapsed, because implementing splitView:canCollapseSubview: prevents uncollapsing
3669 docState.lastPreviewHeight = NSHeight(secondFrame); // cache this
3670 firstFrame.size.height += docState.lastPreviewHeight;
3671 secondFrame.size.height = 0;
3672 } else {
3673 if(docState.lastPreviewHeight <= 0)
3674 docState.lastPreviewHeight = floorf(NSHeight([sender frame]) / 3); // a reasonable value for uncollapsing the first time
3675 firstFrame.size.height = NSHeight(firstFrame) + NSHeight(secondFrame) - docState.lastPreviewHeight;
3676 secondFrame.size.height = docState.lastPreviewHeight;
3677 }
3678 } else if (sender == groupSplitView) {
3679 // zeroth = group, first = table+preview, second = fileview
3680 if (offset == 0) {
3681 if(NSWidth(zerothFrame) > 0){
3682 docState.lastGroupViewWidth = NSWidth(zerothFrame); // cache this
3683 firstFrame.size.width += docState.lastGroupViewWidth;
3684 zerothFrame.size.width = 0;
3685 } else {
3686 if(docState.lastGroupViewWidth <= 0)
3687 docState.lastGroupViewWidth = fminf(120, NSWidth(firstFrame)); // a reasonable value for uncollapsing the first time
3688 firstFrame.size.width -= docState.lastGroupViewWidth;
3689 zerothFrame.size.width = docState.lastGroupViewWidth;
3690 }
3691 } else {
3692 if(NSWidth(secondFrame) > 0){
3693 docState.lastFileViewWidth = NSWidth(secondFrame); // cache this
3694 firstFrame.size.width += docState.lastFileViewWidth;
3695 secondFrame.size.width = 0;
3696 } else {
3697 if(docState.lastFileViewWidth <= 0)
3698 docState.lastFileViewWidth = fminf(120, NSWidth(firstFrame)); // a reasonable value for uncollapsing the first time
3699 firstFrame.size.width -= docState.lastFileViewWidth;
3700 secondFrame.size.width = docState.lastFileViewWidth;
3701 }
3702 }
3703 } else return;
3704
3705 [zerothView setFrame:zerothFrame];
3706 [firstView setFrame:firstFrame];
3707 [secondView setFrame:secondFrame];
3708 [sender adjustSubviews];
3709 [[sender window] invalidateCursorRectsForView:sender];
3710}
3711
3712#pragma mark -
3713
3714- (int)userChangedField:(NSString *)fieldName ofPublications:(NSArray *)pubs from:(NSArray *)oldValues to:(NSArray *)newValues{
3715 int rv = 0;
3716
3717 NSEnumerator *pubEnum = [pubs objectEnumerator];
3718 BibItem *pub;
3719 NSMutableArray *generateKeyPubs = [NSMutableArray arrayWithCapacity:[pubs count]];
3720 NSMutableArray *autofileFiles = [NSMutableArray arrayWithCapacity:[pubs count]];
3721
3722 while(pub = [pubEnum nextObject]){
3723 [[self editorForPublication:pub create:NO( BOOL ) 0] finalizeChanges:nil( ( void * ) 0 )];
3724
3725 // generate cite key if we have enough information
3726 if ([[OFPreferenceWrapper sharedPreferenceWrapper] boolForKey:BDSKCiteKeyAutogenerateKey] && [pub canGenerateAndSetCiteKey])
3727 [generateKeyPubs addObject:pub];
3728
3729 // autofile paper if we have enough information
3730 if ([[OFPreferenceWrapper sharedPreferenceWrapper] boolForKey:BDSKFilePapersAutomaticallyKey]){
3731 NSEnumerator *fileEnum = [[pub localFiles] objectEnumerator];
3732 BDSKLinkedFile *file;
3733 while (file = [fileEnum nextObject])
3734 if ([[pub filesToBeFiled] containsObject:file] && [pub canSetURLForLinkedFile:file])
3735 [autofileFiles addObject:file];
3736 }
3737 }
3738
3739 if([generateKeyPubs count]){
3740 [self generateCiteKeysForPublications:generateKeyPubs];
3741 rv |= 1;
3742 }
3743 if([autofileFiles count]){
3744 [[BDSKFiler sharedFiler] filePapers:autofileFiles fromDocument:self check:NO( BOOL ) 0];
3745 rv |= 2;
3746 }
3747
3748 BDSKScriptHook *scriptHook = [[BDSKScriptHookManager sharedManager] makeScriptHookWithName:BDSKChangeFieldScriptHookName];
3749 if (scriptHook) {
3750 [scriptHook setField:fieldName];
3751 [scriptHook setOldValues:oldValues];
3752 [scriptHook setNewValues:newValues];
3753 [[BDSKScriptHookManager sharedManager] runScriptHook:scriptHook forPublications:pubs document:self];
3754 }
3755
3756 return rv;
3757}
3758
3759- (void)userAddedURL:(NSURL *)aURL forPublication:(BibItem *)pub {
3760 BDSKTypeManager *typeMan = [BDSKTypeManager sharedManager];
3761 if ([aURL isFileURL] == NO( BOOL ) 0 && [NSString isEmptyString:[pub valueOfField:BDSKUrlString]] && [[pub remoteURLs] count] == 1 &&
3762 ([[typeMan requiredFieldsForType:[pub pubType]] containsObject:BDSKUrlString] || [[typeMan optionalFieldsForType:[pub pubType]] containsObject:BDSKUrlString])) {
3763 [pub setField:BDSKUrlString toValue:[aURL absoluteString]];
3764 }
3765
3766 BDSKScriptHook *scriptHook = [[BDSKScriptHookManager sharedManager] makeScriptHookWithName:BDSKAddFileScriptHookName];
3767 if (scriptHook) {
3768 [scriptHook setField:[aURL isFileURL] ? BDSKLocalFileString : BDSKRemoteURLString];
3769 [scriptHook setOldValues:[NSArray array]];
3770 [scriptHook setNewValues:[NSArray arrayWithObjects:[aURL isFileURL] ? [aURL path] : [aURL absoluteString], nil( ( void * ) 0 )]];
3771 [[BDSKScriptHookManager sharedManager] runScriptHook:scriptHook forPublications:[NSArray arrayWithObjects:pub, nil( ( void * ) 0 )] document:self];
3772 }
3773}
3774
3775- (void)userRemovedURL:(NSURL *)aURL forPublication:(BibItem *)pub {
3776 BDSKScriptHook *scriptHook = [[BDSKScriptHookManager sharedManager] makeScriptHookWithName:BDSKRemoveFileScriptHookName];
3777 if (scriptHook) {
3778 [scriptHook setField:([aURL isEqual:[NSNull null]] || [aURL isFileURL]) ? BDSKLocalFileString : BDSKRemoteURLString];
3779 [scriptHook setOldValues:[NSArray arrayWithObjects:[aURL isEqual:[NSNull null]] ? (id)aURL : [aURL isFileURL] ? [aURL path] : [aURL absoluteString], nil( ( void * ) 0 )]];
3780 [scriptHook setNewValues:[NSArray array]];
3781 [[BDSKScriptHookManager sharedManager] runScriptHook:scriptHook forPublications:[NSArray arrayWithObjects:pub, nil( ( void * ) 0 )] document:self];
3782 }
3783}
3784
3785#pragma mark -
3786#pragma mark Protocols forwarding
3787
3788// Declaring protocol conformance in the category headers shuts the compiler up, but causes a hang in -[NSObject conformsToProtocol:], which sucks. Therefore, we use wrapper methods here to call the real (category) implementations.
3789- (void)removeFileContentSearch:(BDSKFileContentSearchController *)controller{
3790 [self privateRemoveFileContentSearch:controller];
3791}
3792
3793- (NSIndexSet *)indexesOfRowsToHighlightInRange:(NSRange)indexRange tableView:(BDSKGroupTableView *)tview{
3794 return [self _indexesOfRowsToHighlightInRange:indexRange tableView:tview];
3795}
3796
3797- (NSIndexSet *)tableViewSingleSelectionIndexes:(BDSKGroupTableView *)tview{
3798 return [self _tableViewSingleSelectionIndexes:tview];
3799}
3800
3801- (void)tableView:(BDSKGroupTableView *)tview doubleClickedOnIconOfRow:(int)row{
3802 [self editGroupAtRow:row];
3803}
3804
3805#pragma mark DisplayName KVO
3806
3807- (void)setFileURL:(NSURL *)absoluteURL{
3808 // make sure that changes in the displayName are observed, as NSDocument doesn't use a KVC compliant method for setting it
3809 [self willChangeValueForKey:@"displayName"];
3810 [super setFileURL:absoluteURL];
3811 [self didChangeValueForKey:@"displayName"];
3812
3813 if (absoluteURL)
3814 [[publications valueForKeyPath:@"@unionOfArrays.files"] makeObjectsPerformSelector:@selector(update)];
3815 [self updateFileViews];
3816 [self updatePreviews];
3817 [[NSNotificationCenter defaultCenter] postNotificationName:BDSKDocumentFileURLDidChangeNotification object:self];
3818}
3819
3820// just create this setter to avoid a run time warning
3821- (void)setDisplayName:(NSString *)newName{}
3822
3823// avoid warning for BDSKOwner protocol conformance
3824- (NSURL *)fileURL {
3825 return [super fileURL];
3826}
3827
3828- (BOOL)isDocument{
3829 return YES( BOOL ) 1;
3830}
3831
3832@end
3833
3834#pragma mark -
3835
3836@implementation NSFileWrapper (BDSKExtensions)
3837
3838- (NSFileWrapper *)addFileWrapperWithPath:(NSString *)path relativeTo:(NSString *)basePath recursive:(BOOL)recursive {
3839 NSFileWrapper *fileWrapper = nil( ( void * ) 0 );
3840 BOOL isDir;
3841 if ([[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir]) {
3842 NSString *filename = [path lastPathComponent];
3843 NSString *relativePath = basePath ? [basePath relativePathToFilename:path] : filename;
3844 NSFileWrapper *container = self;
3845
3846 if ([relativePath isEqualToString:filename] == NO( BOOL ) 0)
3847 container = [self addFileWrapperWithPath:[path stringByDeletingLastPathComponent] relativeTo:basePath recursive:NO( BOOL ) 0];
3848
3849 fileWrapper = [[container fileWrappers] objectForKey:filename];
3850 if (fileWrapper == nil( ( void * ) 0 ) || [fileWrapper isDirectory] != isDir) {
3851 if (isDir)
3852 fileWrapper = [[NSFileWrapper alloc] initDirectoryWithFileWrappers:[NSDictionary dictionary]];
3853 else
3854 fileWrapper = [[NSFileWrapper alloc] initRegularFileWithContents:[NSData dataWithContentsOfFile:path]];
3855 [fileWrapper setPreferredFilename:filename];
3856 [container addFileWrapper:fileWrapper];
3857 [fileWrapper release];
3858 }
3859
3860 if (isDir && recursive) {
3861 NSEnumerator *fileEnum = [[[NSFileManager defaultManager] subpathsAtPath:path] objectEnumerator];
3862 NSString *file;
3863 while (file = [fileEnum nextObject])
3864 [self addFileWrapperWithPath:[path stringByAppendingPathComponent:file] relativeTo:path recursive:YES( BOOL ) 1];
3865 }
3866 }
3867 return fileWrapper;
3868}
3869
3870@end