File: | BibDocument.m |
Location: | line 3284, column 65 |
Description: | dead store |
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 |
129 | NSString *BDSKBibTeXDocumentType = @"BibTeX Database"; |
130 | NSString *BDSKRISDocumentType = @"RIS/Medline File"; |
131 | NSString *BDSKMinimalBibTeXDocumentType = @"Minimal BibTeX Database"; |
132 | NSString *BDSKLTBDocumentType = @"Amsrefs LTB"; |
133 | NSString *BDSKEndNoteDocumentType = @"EndNote XML"; |
134 | NSString *BDSKMODSDocumentType = @"MODS XML"; |
135 | NSString *BDSKAtomDocumentType = @"Atom XML"; |
136 | NSString *BDSKArchiveDocumentType = @"BibTeX and Papers Archive"; |
137 | |
138 | NSString *BDSKReferenceMinerStringPboardType = @"CorePasteboardFlavorType 0x57454253"; |
139 | NSString *BDSKBibItemPboardType = @"edu.ucsd.mmccrack.bibdesk BibItem pboard type"; |
140 | NSString *BDSKWeblocFilePboardType = @"CorePasteboardFlavorType 0x75726C20"; |
141 | |
142 | // private keys used for storing window information in xattrs |
143 | static NSString *BDSKMainWindowExtendedAttributeKey = @"net.sourceforge.bibdesk.BDSKDocumentWindowAttributes"; |
144 | static NSString *BDSKGroupSplitViewFractionKey = @"BDSKGroupSplitViewFractionKey"; |
145 | static NSString *BDSKMainTableSplitViewFractionKey = @"BDSKMainTableSplitViewFractionKey"; |
146 | static NSString *BDSKDocumentWindowFrameKey = @"BDSKDocumentWindowFrameKey"; |
147 | static NSString *BDSKSelectedPublicationsKey = @"BDSKSelectedPublicationsKey"; |
148 | static NSString *BDSKDocumentStringEncodingKey = @"BDSKDocumentStringEncodingKey"; |
149 | static NSString *BDSKDocumentScrollPercentageKey = @"BDSKDocumentScrollPercentageKey"; |
150 | static NSString *BDSKSelectedGroupsKey = @"BDSKSelectedGroupsKey"; |
151 | |
152 | static NSString *BDSKDocumentObservationContext = @"BDSKDocumentObservationContext"; |
153 | |
154 | enum { |
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 | |
399 | static 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 | |
949 | static 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 |
1100 | originalContentsURL:(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 |
2880 | typedef struct __BibItemCiteKeyChangeInfo { |
2881 | BibItem *pub; |
2882 | NSCharacterSet *invalidSet; |
2883 | NSString *key; |
2884 | NSString *oldKey; |
2885 | } _BibItemCiteKeyChangeInfo; |
2886 | |
2887 | static 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 | |
3203 | typedef struct _fileViewObjectContext { |
3204 | CFMutableArrayRef array; |
3205 | NSString *title; |
3206 | } fileViewObjectContext; |
3207 | |
3208 | static 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 | |
3217 | static 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 |