SKBookmarkController.m 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054
  1. //
  2. // SKBookmarkController.m
  3. // Skim
  4. //
  5. // Created by Christiaan Hofman on 3/16/07.
  6. /*
  7. This software is Copyright (c) 2007-2018
  8. Christiaan Hofman. All rights reserved.
  9. Redistribution and use in source and binary forms, with or without
  10. modification, are permitted provided that the following conditions
  11. are met:
  12. - Redistributions of source code must retain the above copyright
  13. notice, this list of conditions and the following disclaimer.
  14. - Redistributions in binary form must reproduce the above copyright
  15. notice, this list of conditions and the following disclaimer in
  16. the documentation and/or other materials provided with the
  17. distribution.
  18. - Neither the name of Christiaan Hofman nor the names of any
  19. contributors may be used to endorse or promote products derived
  20. from this software without specific prior written permission.
  21. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  22. "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  23. LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  24. A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  25. OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  26. SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  27. LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  28. DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  29. THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  30. (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  31. OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  32. */
  33. #import "SKBookmarkController.h"
  34. #import "SKBookmark.h"
  35. #import "SKAlias.h"
  36. //#import "SKStatusBar.h"
  37. #import "SKTextWithIconCell.h"
  38. #import "SKToolbarItem.h"
  39. //#import "SKStringConstants.h"
  40. #import "SKSeparatorCell.h"
  41. #import "NSMenu_SKExtensions.h"
  42. //#import "NSURL_SKExtensions.h"
  43. //#import "NSString_SKExtensions.h"
  44. //#import "NSEvent_SKExtensions.h"
  45. //#import "NSImage_SKExtensions.h"
  46. //#import "NSShadow_SKExtensions.h"
  47. //#import "NSView_SKExtensions.h"
  48. #import <PDF_Reader_Pro-Swift.h>
  49. #define SKPasteboardTypeBookmarkRows @"net.sourceforge.skim-app.pasteboard.bookmarkrows"
  50. #define SKBookmarksToolbarIdentifier @"SKBookmarksToolbarIdentifier"
  51. #define SKBookmarksNewFolderToolbarItemIdentifier @"SKBookmarksNewFolderToolbarItemIdentifier"
  52. #define SKBookmarksNewSeparatorToolbarItemIdentifier @"SKBookmarksNewSeparatorToolbarItemIdentifier"
  53. #define SKBookmarksDeleteToolbarItemIdentifier @"SKBookmarksDeleteToolbarItemIdentifier"
  54. #define SKBookmarksWindowFrameAutosaveName @"SKBookmarksWindow"
  55. #define LABEL_COLUMNID @"label"
  56. #define FILE_COLUMNID @"file"
  57. #define PAGE_COLUMNID @"page"
  58. #define SKMaximumDocumentPageHistoryCountKey @"SKMaximumDocumentPageHistoryCount"
  59. #define BOOKMARKS_KEY @"bookmarks"
  60. #define RECENTDOCUMENTS_KEY @"recentDocuments"
  61. #define PAGEINDEX_KEY @"pageIndex"
  62. #define SCALE_KEY @"scale"
  63. #define ALIAS_KEY @"alias"
  64. #define ALIASDATA_KEY @"_BDAlias"
  65. #define SNAPSHOTS_KEY @"snapshots"
  66. #define CHILDREN_KEY @"children"
  67. #define LABEL_KEY @"label"
  68. NSString *SKLastOpenFileNamesKey = @"SKLastOpenFileNames";
  69. NSString *SKShowBookmarkStatusBarKey = @"SKShowBookmarkStatusBar";
  70. static char SKBookmarkPropertiesObservationContext;
  71. static NSString *SKBookmarksIdentifier = nil;
  72. static NSArray *minimumCoverForBookmarks(NSArray *items);
  73. @interface SKBookmarkController (SKPrivate)
  74. - (void)setupToolbar;
  75. - (void)handleApplicationWillTerminateNotification:(NSNotification *)notification;
  76. - (void)endEditing;
  77. - (void)startObservingBookmarks:(NSArray *)newBookmarks;
  78. - (void)stopObservingBookmarks:(NSArray *)oldBookmarks;
  79. @end
  80. @interface SKBookmarkController ()
  81. @property (nonatomic, readonly) NSUndoManager *undoManager;
  82. @property (nonatomic, retain) NSArray *draggedBookmarks;
  83. @end
  84. @implementation SKBookmarkController
  85. static SKBookmarkController *sharedBookmarkController = nil;
  86. static NSUInteger maxRecentDocumentsCount = 0;
  87. + (SKBookmarkController *)sharedBookmarkController {
  88. static dispatch_once_t onceToken;
  89. dispatch_once(&onceToken, ^{
  90. sharedBookmarkController = [[SKBookmarkController alloc] init];
  91. });
  92. return sharedBookmarkController;
  93. }
  94. + (void)initialize {
  95. // SKINITIALIZE;
  96. maxRecentDocumentsCount = [[NSUserDefaults standardUserDefaults] integerForKey:SKMaximumDocumentPageHistoryCountKey];
  97. if (maxRecentDocumentsCount == 0)
  98. maxRecentDocumentsCount = 50;
  99. SKBookmarksIdentifier = [[[NSBundle mainBundle] bundleIdentifier] stringByAppendingString:@".bookmarks"];
  100. }
  101. - (SKBookmarkController *)init {
  102. if (sharedBookmarkController == nil) {
  103. self = [super initWithWindowNibName:@"BookmarksWindow"];
  104. if (self) {
  105. NSDictionary *bookmarkDictionary = [[NSUserDefaults standardUserDefaults] persistentDomainForName:SKBookmarksIdentifier];
  106. recentDocuments = [[NSMutableArray alloc] init];
  107. for (NSDictionary *info in [bookmarkDictionary objectForKey:RECENTDOCUMENTS_KEY]) {
  108. NSMutableDictionary *mutableInfo = [info mutableCopy];
  109. [recentDocuments addObject:mutableInfo];
  110. }
  111. _bookmarkRoot = [[SKBookmark alloc] initRootWithChildrenProperties:[bookmarkDictionary objectForKey:BOOKMARKS_KEY]];
  112. [self startObservingBookmarks:[NSArray arrayWithObject:_bookmarkRoot]];
  113. [[NSNotificationCenter defaultCenter] addObserver:self
  114. selector:@selector(handleApplicationWillTerminateNotification:)
  115. name:NSApplicationWillTerminateNotification
  116. object:NSApp];
  117. NSArray *lastOpenFiles = [[NSUserDefaults standardUserDefaults] arrayForKey:SKLastOpenFileNamesKey];
  118. if ([lastOpenFiles count] > 0)
  119. previousSession = [[SKBookmark alloc] initSessionWithSetups:lastOpenFiles label:NSLocalizedString(@"Restore Previous Session", @"Menu item title")];
  120. }
  121. sharedBookmarkController = self;
  122. } else if (self != sharedBookmarkController) {
  123. NSLog(@"Attempt to allocate second instance of %@", [self class]);
  124. }
  125. return self;
  126. }
  127. - (void)dealloc {
  128. [self stopObservingBookmarks:[NSArray arrayWithObject:_bookmarkRoot]];
  129. [[NSNotificationCenter defaultCenter] removeObserver:self];
  130. }
  131. - (void)windowDidLoad {
  132. [self setupToolbar];
  133. if ([[self window] respondsToSelector:@selector(setTabbingMode:)])
  134. [[self window] setTabbingMode:NSWindowTabbingModeDisallowed];
  135. [self setWindowFrameAutosaveName:SKBookmarksWindowFrameAutosaveName];
  136. [[self window] setAutorecalculatesContentBorderThickness:NO forEdge:NSMinYEdge];
  137. if ([[NSUserDefaults standardUserDefaults] boolForKey:SKShowBookmarkStatusBarKey] == NO)
  138. [self toggleStatusBar:nil];
  139. else
  140. [[self window] setContentBorderThickness:22.0 forEdge:NSMinYEdge];
  141. // [_outlineView setTypeSelectHelper:[SKTypeSelectHelper typeSelectHelper]];
  142. [_outlineView registerForDraggedTypes:[NSArray arrayWithObjects:SKPasteboardTypeBookmarkRows, (NSString *)kUTTypeFileURL, NSFilenamesPboardType, nil]];
  143. [_outlineView setDoubleAction:@selector(doubleClickBookmark:)];
  144. // [_outlineView setSupportsQuickLook:YES];
  145. }
  146. - (void)updateStatus {
  147. NSInteger row = [_outlineView selectedRow];
  148. NSString *message = @"";
  149. if (row != -1) {
  150. SKBookmark *bookmark = [_outlineView itemAtRow:row];
  151. if ([bookmark bookmarkType] == SKBookmarkTypeBookmark) {
  152. message = [[bookmark fileURL] path];
  153. } else if ([bookmark bookmarkType] == SKBookmarkTypeFolder) {
  154. NSInteger count = [bookmark countOfChildren];
  155. message = count == 1 ? NSLocalizedString(@"1 item", @"Bookmark folder description") : [NSString stringWithFormat:NSLocalizedString(@"%ld items", @"Bookmark folder description"), (long)count];
  156. }
  157. }
  158. // [statusBar setLeftStringValue:message ?: @""];
  159. }
  160. #pragma mark Recent Documents
  161. - (NSDictionary *)recentDocumentInfoAtURL:(NSURL *)fileURL {
  162. NSString *path = [fileURL path];
  163. for (NSMutableDictionary *info in recentDocuments) {
  164. SKAlias *alias = [info valueForKey:ALIAS_KEY];
  165. if (alias == nil) {
  166. alias = [SKAlias aliasWithData:[info valueForKey:ALIASDATA_KEY]];
  167. [info setValue:alias forKey:ALIAS_KEY];
  168. }
  169. if ([[[alias fileURLNoUI] path] isCaseInsensitiveEqual:path])
  170. return info;
  171. }
  172. return nil;
  173. }
  174. - (void)addRecentDocumentForURL:(NSURL *)fileURL pageIndex:(NSUInteger)pageIndex scaleFactor:(CGFloat)factor snapshots:(NSArray *)setups {
  175. if (fileURL == nil)
  176. return;
  177. NSDictionary *info = [self recentDocumentInfoAtURL:fileURL];
  178. if (info)
  179. [recentDocuments removeObjectIdenticalTo:info];
  180. SKAlias *alias = [SKAlias aliasWithURL:fileURL];
  181. if (alias) {
  182. NSMutableDictionary *bm = [NSMutableDictionary dictionaryWithObjectsAndKeys:
  183. [NSNumber numberWithUnsignedInteger:pageIndex], PAGEINDEX_KEY,
  184. [NSNumber numberWithFloat:factor],SCALE_KEY,
  185. [alias data], ALIASDATA_KEY,
  186. alias, ALIAS_KEY, [setups count] ? setups : nil, SNAPSHOTS_KEY, nil];
  187. [recentDocuments insertObject:bm atIndex:0];
  188. if ([recentDocuments count] > maxRecentDocumentsCount)
  189. [recentDocuments removeLastObject];
  190. }
  191. }
  192. - (NSUInteger)pageIndexForRecentDocumentAtURL:(NSURL *)fileURL {
  193. if (fileURL == nil)
  194. return NSNotFound;
  195. NSNumber *pageIndex = [[self recentDocumentInfoAtURL:fileURL] objectForKey:PAGEINDEX_KEY];
  196. return pageIndex == nil ? NSNotFound : [pageIndex unsignedIntegerValue];
  197. }
  198. - (CGFloat)scaleFactorForRecentDocumentAtURL:(NSURL *)fileURL {
  199. if (fileURL == nil)
  200. return 0;
  201. NSNumber *scaleFactor = [[self recentDocumentInfoAtURL:fileURL] objectForKey:SCALE_KEY];
  202. return scaleFactor == nil ? 0 : [scaleFactor floatValue];
  203. }
  204. - (NSArray *)snapshotsForRecentDocumentAtURL:(NSURL *)fileURL {
  205. if (fileURL == nil)
  206. return nil;
  207. NSArray *setups = [[self recentDocumentInfoAtURL:fileURL] objectForKey:SNAPSHOTS_KEY];
  208. return [setups count] ? setups : nil;
  209. }
  210. #pragma mark Bookmarks support
  211. - (void)getInsertionFolder:(SKBookmark **)bookmarkPtr childIndex:(NSUInteger *)indexPtr {
  212. NSInteger rowIndex = [_outlineView clickedRow];
  213. NSIndexSet *indexes = [_outlineView selectedRowIndexes];
  214. if (rowIndex != -1 && [indexes containsIndex:rowIndex] == NO)
  215. indexes = [NSIndexSet indexSetWithIndex:rowIndex];
  216. rowIndex = [indexes lastIndex];
  217. SKBookmark *item = _bookmarkRoot;
  218. NSUInteger idx = [_bookmarkRoot countOfChildren];
  219. if (rowIndex != NSNotFound) {
  220. SKBookmark *selectedItem = [_outlineView itemAtRow:rowIndex];
  221. if ([_outlineView isItemExpanded:selectedItem]) {
  222. item = selectedItem;
  223. idx = [item countOfChildren];
  224. } else {
  225. item = [selectedItem parent];
  226. idx = [[item children] indexOfObject:selectedItem] + 1;
  227. }
  228. }
  229. *bookmarkPtr = item;
  230. *indexPtr = idx;
  231. }
  232. - (IBAction)openBookmark:(id)sender {
  233. [((SKBookmark *)[sender representedObject]) open];
  234. }
  235. - (IBAction)doubleClickBookmark:(id)sender {
  236. NSInteger row = [_outlineView clickedRow];
  237. SKBookmark *bm = row == -1 ? nil : [_outlineView itemAtRow:row];
  238. if (bm && ([bm bookmarkType] == SKBookmarkTypeBookmark || [bm bookmarkType] == SKBookmarkTypeSession))
  239. [bm open];
  240. }
  241. - (IBAction)insertBookmarkFolder:(id)sender {
  242. SKBookmark *folder = [SKBookmark bookmarkFolderWithLabel:NSLocalizedString(@"Folder", @"default folder name")];
  243. SKBookmark *item = nil;
  244. NSUInteger idx = 0;
  245. [self getInsertionFolder:&item childIndex:&idx];
  246. [item insertObject:folder inChildrenAtIndex:idx];
  247. NSInteger row = [_outlineView rowForItem:folder];
  248. [_outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
  249. [_outlineView editColumn:0 row:row withEvent:nil select:YES];
  250. }
  251. - (IBAction)insertBookmarkSeparator:(id)sender {
  252. SKBookmark *separator = [SKBookmark bookmarkSeparator];
  253. SKBookmark *item = nil;
  254. NSUInteger idx = 0;
  255. [self getInsertionFolder:&item childIndex:&idx];
  256. [item insertObject:separator inChildrenAtIndex:idx];
  257. NSInteger row = [_outlineView rowForItem:separator];
  258. [_outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
  259. }
  260. - (IBAction)addBookmark:(id)sender {
  261. NSOpenPanel *openPanel = [NSOpenPanel openPanel];
  262. NSMutableArray *types = [NSMutableArray array];
  263. for (NSString *docClass in [[NSDocumentController sharedDocumentController] documentClassNames])
  264. [types addObjectsFromArray:[NSClassFromString(docClass) readableTypes]];
  265. [openPanel setAllowsMultipleSelection:YES];
  266. [openPanel setCanChooseDirectories:YES];
  267. [openPanel setAllowedFileTypes:types];
  268. [openPanel beginSheetModalForWindow:[self window] completionHandler:^(NSInteger result){
  269. if (result == NSFileHandlingPanelOKButton) {
  270. NSArray *newBookmarks = [SKBookmark bookmarksForURLs:[openPanel URLs]];
  271. if ([newBookmarks count] > 0) {
  272. SKBookmark *item = nil;
  273. NSUInteger anIndex = 0;
  274. [self getInsertionFolder:&item childIndex:&anIndex];
  275. NSMutableIndexSet *indexes = [NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(anIndex, [newBookmarks count])];
  276. [[item mutableArrayValueForKey:@"children"] insertObjects:newBookmarks atIndexes:indexes];
  277. if (item == _bookmarkRoot || [_outlineView isItemExpanded:item]) {
  278. if (item != _bookmarkRoot)
  279. [indexes shiftIndexesStartingAtIndex:0 by:[_outlineView rowForItem:item] + 1];
  280. [_outlineView selectRowIndexes:indexes byExtendingSelection:NO];
  281. }
  282. }
  283. }
  284. }];
  285. }
  286. - (IBAction)deleteBookmark:(id)sender {
  287. [_outlineView delete:sender];
  288. }
  289. - (IBAction)toggleStatusBar:(id)sender {
  290. // [[NSUserDefaults standardUserDefaults] setBool:(NO == [statusBar isVisible]) forKey:SKShowBookmarkStatusBarKey];
  291. // [statusBar toggleBelowView:[_outlineView enclosingScrollView] animate:sender != nil];
  292. }
  293. - (NSArray *)clickedBookmarks {
  294. NSArray *items = nil;
  295. NSInteger row = [_outlineView clickedRow];
  296. if (row != -1) {
  297. NSIndexSet *indexes = [_outlineView selectedRowIndexes];
  298. if ([indexes containsIndex:row] == NO)
  299. indexes = [NSIndexSet indexSetWithIndex:row];
  300. items = [_outlineView itemsAtRowIndexes:indexes];
  301. }
  302. return items;
  303. }
  304. - (IBAction)deleteBookmarks:(id)sender {
  305. NSArray *items = minimumCoverForBookmarks([self clickedBookmarks]);
  306. [self endEditing];
  307. for (SKBookmark *item in [items reverseObjectEnumerator]) {
  308. SKBookmark *parent = [item parent];
  309. NSUInteger itemIndex = [[parent children] indexOfObject:item];
  310. if (itemIndex != NSNotFound)
  311. [parent removeObjectFromChildrenAtIndex:itemIndex];
  312. }
  313. }
  314. - (IBAction)openBookmarks:(id)sender {
  315. NSArray *items = minimumCoverForBookmarks([self clickedBookmarks]);
  316. for (SKBookmark *item in [minimumCoverForBookmarks(items) reverseObjectEnumerator])
  317. [item open];
  318. }
  319. - (IBAction)previewBookmarks:(id)sender {
  320. if ([QLPreviewPanel sharedPreviewPanelExists] && [[QLPreviewPanel sharedPreviewPanel] isVisible]) {
  321. [[QLPreviewPanel sharedPreviewPanel] orderOut:nil];
  322. } else {
  323. NSInteger row = [_outlineView clickedRow];
  324. [_outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
  325. [[QLPreviewPanel sharedPreviewPanel] makeKeyAndOrderFront:nil];
  326. }
  327. }
  328. #pragma mark NSMenu delegate methods
  329. - (void)addItemForBookmark:(SKBookmark *)bookmark toMenu:(NSMenu *)menu isFolder:(BOOL)isFolder isAlternate:(BOOL)isAlternate {
  330. NSMenuItem *item = nil;
  331. if (isFolder) {
  332. item = [menu addItemWithSubmenuAndTitle:[bookmark label]];
  333. [[item submenu] setDelegate:self];
  334. } else {
  335. item = [menu addItemWithTitle:[bookmark label] action:@selector(openBookmark:) target:self];
  336. }
  337. [item setRepresentedObject:bookmark];
  338. if (isAlternate) {
  339. [item setKeyEquivalentModifierMask:NSAlternateKeyMask];
  340. [item setAlternate:YES];
  341. [item setImageAndSize:[bookmark alternateIcon]];
  342. } else {
  343. [item setImageAndSize:[bookmark icon]];
  344. }
  345. }
  346. - (void)menuNeedsUpdate:(NSMenu *)menu {
  347. if (menu == [_outlineView menu]) {
  348. NSInteger row = [_outlineView clickedRow];
  349. [menu removeAllItems];
  350. if (row != -1) {
  351. [menu addItemWithTitle:NSLocalizedString(@"Remove", @"Menu item title") action:@selector(deleteBookmarks:) target:self];
  352. [menu addItemWithTitle:NSLocalizedString(@"Open", @"Menu item title") action:@selector(openBookmarks:) target:self];
  353. [menu addItemWithTitle:NSLocalizedString(@"Quick Look", @"Menu item title") action:@selector(previewBookmarks:) target:self];
  354. [menu addItem:[NSMenuItem separatorItem]];
  355. }
  356. [menu addItemWithTitle:NSLocalizedString(@"New Folder", @"Menu item title") action:@selector(insertBookmarkFolder:) target:self];
  357. [menu addItemWithTitle:NSLocalizedString(@"New Separator", @"Menu item title") action:@selector(insertBookmarkSeparator:) target:self];
  358. } else {
  359. NSMenu *supermenu = [menu supermenu];
  360. NSInteger idx = [supermenu indexOfItemWithSubmenu:menu];
  361. SKBookmark *bm = nil;
  362. if (supermenu == [NSApp mainMenu])
  363. bm = [self bookmarkRoot];
  364. else if (idx >= 0)
  365. bm = [[supermenu itemAtIndex:idx] representedObject];
  366. if ([bm isKindOfClass:[SKBookmark class]]) {
  367. NSArray *bookmarks = [bm children];
  368. NSInteger i = [menu numberOfItems];
  369. while (i-- > 0 && ([[menu itemAtIndex:i] isSeparatorItem] || [[menu itemAtIndex:i] representedObject]))
  370. [menu removeItemAtIndex:i];
  371. if (supermenu == [NSApp mainMenu] && previousSession) {
  372. [menu addItem:[NSMenuItem separatorItem]];
  373. [self addItemForBookmark:previousSession toMenu:menu isFolder:NO isAlternate:NO];
  374. [self addItemForBookmark:previousSession toMenu:menu isFolder:YES isAlternate:YES];
  375. }
  376. if ([menu numberOfItems] > 0 && [bookmarks count] > 0)
  377. [menu addItem:[NSMenuItem separatorItem]];
  378. for (bm in bookmarks) {
  379. switch ([bm bookmarkType]) {
  380. case SKBookmarkTypeFolder:
  381. [self addItemForBookmark:bm toMenu:menu isFolder:YES isAlternate:NO];
  382. [self addItemForBookmark:bm toMenu:menu isFolder:NO isAlternate:YES];
  383. break;
  384. case SKBookmarkTypeSession:
  385. [self addItemForBookmark:bm toMenu:menu isFolder:NO isAlternate:NO];
  386. [self addItemForBookmark:bm toMenu:menu isFolder:YES isAlternate:YES];
  387. break;
  388. case SKBookmarkTypeSeparator:
  389. [menu addItem:[NSMenuItem separatorItem]];
  390. break;
  391. default:
  392. [self addItemForBookmark:bm toMenu:menu isFolder:NO isAlternate:NO];
  393. break;
  394. }
  395. }
  396. }
  397. }
  398. }
  399. // avoid rebuilding the bookmarks menu on every key event
  400. - (BOOL)menuHasKeyEquivalent:(NSMenu *)menu forEvent:(NSEvent *)event target:(id *)target action:(SEL *)action { return NO; }
  401. #pragma mark Undo support
  402. - (NSUndoManager *)undoManager {
  403. if(undoManager == nil)
  404. undoManager = [[NSUndoManager alloc] init];
  405. return undoManager;
  406. }
  407. - (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)sender {
  408. return [self undoManager];
  409. }
  410. - (void)startObservingBookmarks:(NSArray *)newBookmarks {
  411. for (SKBookmark *bm in newBookmarks) {
  412. if ([bm bookmarkType] != SKBookmarkTypeSeparator) {
  413. [bm addObserver:self forKeyPath:LABEL_KEY options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:&SKBookmarkPropertiesObservationContext];
  414. [bm addObserver:self forKeyPath:PAGEINDEX_KEY options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:&SKBookmarkPropertiesObservationContext];
  415. if ([bm bookmarkType] == SKBookmarkTypeFolder) {
  416. [bm addObserver:self forKeyPath:CHILDREN_KEY options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:&SKBookmarkPropertiesObservationContext];
  417. [self startObservingBookmarks:[bm children]];
  418. }
  419. }
  420. }
  421. }
  422. - (void)stopObservingBookmarks:(NSArray *)oldBookmarks {
  423. for (SKBookmark *bm in oldBookmarks) {
  424. if ([bm bookmarkType] != SKBookmarkTypeSeparator) {
  425. [bm removeObserver:self forKeyPath:LABEL_KEY];
  426. [bm removeObserver:self forKeyPath:PAGEINDEX_KEY];
  427. if ([bm bookmarkType] == SKBookmarkTypeFolder) {
  428. [bm removeObserver:self forKeyPath:CHILDREN_KEY];
  429. [self stopObservingBookmarks:[bm children]];
  430. }
  431. }
  432. }
  433. }
  434. - (void)setChildren:(NSArray *)newChildren ofBookmark:(SKBookmark *)bookmark {
  435. [self endEditing];
  436. [[bookmark mutableArrayValueForKey:CHILDREN_KEY] setArray:newChildren];
  437. }
  438. - (void)insertObjects:(NSArray *)newChildren inChildrenOfBookmark:(SKBookmark *)bookmark atIndexes:(NSIndexSet *)indexes {
  439. [[bookmark mutableArrayValueForKey:CHILDREN_KEY] insertObjects:newChildren atIndexes:indexes];
  440. }
  441. - (void)removeObjectsFromChildrenOfBookmark:(SKBookmark *)bookmark atIndexes:(NSIndexSet *)indexes {
  442. [self endEditing];
  443. [[bookmark mutableArrayValueForKey:CHILDREN_KEY] removeObjectsAtIndexes:indexes];
  444. }
  445. #pragma mark KVO
  446. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
  447. if (context == &SKBookmarkPropertiesObservationContext) {
  448. SKBookmark *bookmark = (SKBookmark *)object;
  449. id newValue = [change objectForKey:NSKeyValueChangeNewKey];
  450. id oldValue = [change objectForKey:NSKeyValueChangeOldKey];
  451. NSIndexSet *indexes = [[change objectForKey:NSKeyValueChangeIndexesKey] copy];
  452. if ([newValue isEqual:[NSNull null]]) newValue = nil;
  453. if ([oldValue isEqual:[NSNull null]]) oldValue = nil;
  454. switch ([[change objectForKey:NSKeyValueChangeKindKey] unsignedIntegerValue]) {
  455. case NSKeyValueChangeSetting:
  456. if ([keyPath isEqualToString:CHILDREN_KEY]) {
  457. NSMutableArray *old = [NSMutableArray arrayWithArray:oldValue];
  458. NSMutableArray *new = [NSMutableArray arrayWithArray:newValue];
  459. [old removeObjectsInArray:newValue];
  460. [new removeObjectsInArray:oldValue];
  461. [self stopObservingBookmarks:old];
  462. [self startObservingBookmarks:new];
  463. [[[self undoManager] prepareWithInvocationTarget:self] setChildren:[oldValue copy] ofBookmark:bookmark];
  464. } else if ([keyPath isEqualToString:LABEL_KEY]) {
  465. [[[self undoManager] prepareWithInvocationTarget:bookmark] setLabel:oldValue];
  466. } else if ([keyPath isEqualToString:PAGEINDEX_KEY]) {
  467. [[[self undoManager] prepareWithInvocationTarget:bookmark] setPageIndex:[oldValue unsignedIntegerValue]];
  468. }
  469. break;
  470. case NSKeyValueChangeInsertion:
  471. if ([keyPath isEqualToString:CHILDREN_KEY]) {
  472. [self startObservingBookmarks:newValue];
  473. [[[self undoManager] prepareWithInvocationTarget:self] removeObjectsFromChildrenOfBookmark:bookmark atIndexes:indexes];
  474. }
  475. break;
  476. case NSKeyValueChangeRemoval:
  477. if ([keyPath isEqualToString:CHILDREN_KEY]) {
  478. [self stopObservingBookmarks:oldValue];
  479. [[[self undoManager] prepareWithInvocationTarget:self] insertObjects:[oldValue copy] inChildrenOfBookmark:bookmark atIndexes:indexes];
  480. }
  481. break;
  482. case NSKeyValueChangeReplacement:
  483. if ([keyPath isEqualToString:CHILDREN_KEY]) {
  484. [self stopObservingBookmarks:oldValue];
  485. [self startObservingBookmarks:newValue];
  486. [[[self undoManager] prepareWithInvocationTarget:self] removeObjectsFromChildrenOfBookmark:bookmark atIndexes:indexes];
  487. [[[self undoManager] prepareWithInvocationTarget:self] insertObjects:[oldValue copy] inChildrenOfBookmark:bookmark atIndexes:indexes];
  488. }
  489. break;
  490. }
  491. [_outlineView reloadData];
  492. } else {
  493. [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
  494. }
  495. }
  496. #pragma mark Notification handlers
  497. - (void)handleApplicationWillTerminateNotification:(NSNotification *)notification {
  498. [recentDocuments makeObjectsPerformSelector:@selector(removeObjectForKey:) withObject:ALIAS_KEY];
  499. NSDictionary *bookmarksDictionary = [NSDictionary dictionaryWithObjectsAndKeys:[[_bookmarkRoot children] valueForKey:@"properties"], BOOKMARKS_KEY, recentDocuments, RECENTDOCUMENTS_KEY, nil];
  500. [[NSUserDefaults standardUserDefaults] setPersistentDomain:bookmarksDictionary forName:SKBookmarksIdentifier];
  501. }
  502. - (void)endEditing {
  503. if ([_outlineView editedRow] && [[self window] makeFirstResponder:_outlineView] == NO)
  504. [[self window] endEditingFor:nil];
  505. }
  506. #pragma mark NSOutlineView datasource methods
  507. static NSArray *minimumCoverForBookmarks(NSArray *items) {
  508. SKBookmark *lastBm = nil;
  509. NSMutableArray *minimalCover = [NSMutableArray array];
  510. for (SKBookmark *bm in items) {
  511. if ([bm isDescendantOf:lastBm] == NO) {
  512. [minimalCover addObject:bm];
  513. lastBm = bm;
  514. }
  515. }
  516. return minimalCover;
  517. }
  518. - (NSInteger)outlineView:(NSOutlineView *)ov numberOfChildrenOfItem:(id)item {
  519. if (item == nil) item = _bookmarkRoot;
  520. return [item bookmarkType] == SKBookmarkTypeFolder ? [item countOfChildren] : 0;
  521. }
  522. - (BOOL)outlineView:(NSOutlineView *)ov isItemExpandable:(id)item {
  523. return [item bookmarkType] == SKBookmarkTypeFolder;
  524. }
  525. - (id)outlineView:(NSOutlineView *)ov child:(NSInteger)anIndex ofItem:(id)item {
  526. return [(item ?: _bookmarkRoot) objectInChildrenAtIndex:anIndex];
  527. }
  528. - (id)outlineView:(NSOutlineView *)ov objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
  529. NSString *tcID = [tableColumn identifier];
  530. if ([tcID isEqualToString:LABEL_COLUMNID]) {
  531. return [NSDictionary dictionaryWithObjectsAndKeys:[item label], SKTextWithIconStringKey, [item icon], SKTextWithIconImageKey, nil];
  532. } else if ([tcID isEqualToString:FILE_COLUMNID]) {
  533. if ([item bookmarkType] == SKBookmarkTypeFolder || [item bookmarkType] == SKBookmarkTypeSession) {
  534. NSInteger count = [item countOfChildren];
  535. return count == 1 ? NSLocalizedString(@"1 item", @"Bookmark folder description") : [NSString stringWithFormat:NSLocalizedString(@"%ld items", @"Bookmark folder description"), (long)count];
  536. } else {
  537. return [[[item fileURL] path] stringByAbbreviatingWithTildeInPath];
  538. }
  539. } else if ([tcID isEqualToString:PAGE_COLUMNID]) {
  540. return [item pageNumber];
  541. }
  542. return nil;
  543. }
  544. - (void)outlineView:(NSOutlineView *)ov setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
  545. NSString *tcID = [tableColumn identifier];
  546. if ([tcID isEqualToString:LABEL_COLUMNID]) {
  547. NSString *newlabel = [object valueForKey:SKTextWithIconStringKey] ?: @"";
  548. if ([newlabel isEqualToString:[item label]] == NO)
  549. [item setLabel:newlabel];
  550. } else if ([tcID isEqualToString:PAGE_COLUMNID]) {
  551. if ([object isEqual:[item pageNumber]] == NO)
  552. [item setPageNumber:object];
  553. }
  554. }
  555. - (BOOL)outlineView:(NSOutlineView *)ov writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard {
  556. [self setDraggedBookmarks:minimumCoverForBookmarks(items)];
  557. [pboard clearContents];
  558. [pboard setData:[NSData data] forType:SKPasteboardTypeBookmarkRows];
  559. return YES;
  560. }
  561. - (NSDragOperation)outlineView:(NSOutlineView *)ov validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(NSInteger)anIndex {
  562. NSDragOperation dragOp = NSDragOperationNone;
  563. if (anIndex != NSOutlineViewDropOnItemIndex) {
  564. NSPasteboard *pboard = [info draggingPasteboard];
  565. if ([pboard canReadItemWithDataConformingToTypes:[NSArray arrayWithObjects:SKPasteboardTypeBookmarkRows, nil]] &&
  566. [info draggingSource] == ov)
  567. dragOp = NSDragOperationMove;
  568. else if ([self canReadFileURLFromPasteboard:pboard])
  569. dragOp = NSDragOperationEvery;
  570. }
  571. return dragOp;
  572. }
  573. - (BOOL)outlineView:(NSOutlineView *)ov acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)anIndex {
  574. NSPasteboard *pboard = [info draggingPasteboard];
  575. if ([pboard canReadItemWithDataConformingToTypes:[NSArray arrayWithObjects:SKPasteboardTypeBookmarkRows, nil]] &&
  576. [info draggingSource] == ov) {
  577. NSMutableArray *movedBookmarks = [NSMutableArray array];
  578. NSMutableIndexSet *indexes = [NSMutableIndexSet indexSet];
  579. if (item == nil) item = _bookmarkRoot;
  580. [self endEditing];
  581. for (SKBookmark *bookmark in [self draggedBookmarks]) {
  582. SKBookmark *parent = [bookmark parent];
  583. NSInteger bookmarkIndex = [[parent children] indexOfObject:bookmark];
  584. if (item == parent) {
  585. if (anIndex > bookmarkIndex)
  586. anIndex--;
  587. if (anIndex == bookmarkIndex)
  588. continue;
  589. }
  590. [parent removeObjectFromChildrenAtIndex:bookmarkIndex];
  591. [(SKBookmark *)item insertObject:bookmark inChildrenAtIndex:anIndex++];
  592. [movedBookmarks addObject:bookmark];
  593. }
  594. for (SKBookmark *bookmark in movedBookmarks) {
  595. NSInteger row = [_outlineView rowForItem:bookmark];
  596. if (row != -1)
  597. [indexes addIndex:row];
  598. }
  599. if ([indexes count])
  600. [_outlineView selectRowIndexes:indexes byExtendingSelection:NO];
  601. return YES;
  602. } else {
  603. NSArray *urls = [self readFileURLsFromPasteboard:pboard];
  604. NSArray *newBookmarks = [SKBookmark bookmarksForURLs:urls];
  605. if ([newBookmarks count] > 0) {
  606. [self endEditing];
  607. if (item == nil) item = _bookmarkRoot;
  608. NSMutableIndexSet *indexes = [NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(anIndex, [newBookmarks count])];
  609. [[item mutableArrayValueForKey:@"children"] insertObjects:newBookmarks atIndexes:indexes];
  610. if (item == _bookmarkRoot || [_outlineView isItemExpanded:item]) {
  611. if (item != _bookmarkRoot)
  612. [indexes shiftIndexesStartingAtIndex:0 by:[_outlineView rowForItem:item] + 1];
  613. [_outlineView selectRowIndexes:indexes byExtendingSelection:NO];
  614. }
  615. return YES;
  616. }
  617. return NO;
  618. }
  619. return NO;
  620. }
  621. - (void)outlineView:(NSOutlineView *)ov dragEndedWithOperation:(NSDragOperation)operation {
  622. [self setDraggedBookmarks:nil];
  623. }
  624. #pragma mark NSOutlineView delegate methods
  625. - (NSCell *)outlineView:(NSOutlineView *)ov dataCellForTableColumn:(NSTableColumn *)tableColumn item:(id)item {
  626. if (tableColumn == nil)
  627. return [item bookmarkType] == SKBookmarkTypeSeparator ? [[SKSeparatorCell alloc] init] : nil;
  628. return [tableColumn dataCellForRow:[ov rowForItem:item]];
  629. }
  630. - (void)outlineView:(NSOutlineView *)ov willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item {
  631. if ([[tableColumn identifier] isEqualToString:FILE_COLUMNID]) {
  632. if ([item bookmarkType] == SKBookmarkTypeFolder || [item bookmarkType] == SKBookmarkTypeSession)
  633. [cell setTextColor:[NSColor disabledControlTextColor]];
  634. else
  635. [cell setTextColor:[NSColor controlTextColor]];
  636. }
  637. }
  638. - (BOOL)outlineView:(NSOutlineView *)ov shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item {
  639. NSString *tcID = [tableColumn identifier];
  640. if ([tcID isEqualToString:LABEL_COLUMNID])
  641. return [item bookmarkType] != SKBookmarkTypeSeparator;
  642. else if ([tcID isEqualToString:PAGE_COLUMNID])
  643. return [item pageIndex] != NSNotFound;
  644. return NO;
  645. }
  646. - (NSString *)outlineView:(NSOutlineView *)ov toolTipForCell:(NSCell *)cell rect:(NSRectPointer)rect tableColumn:(NSTableColumn *)tc item:(id)item mouseLocation:(NSPoint)mouseLocation {
  647. NSString *tcID = [tc identifier];
  648. if ([tcID isEqualToString:LABEL_COLUMNID]) {
  649. return [item label];
  650. } else if ([tcID isEqualToString:FILE_COLUMNID]) {
  651. if ([item bookmarkType] == SKBookmarkTypeSession) {
  652. return [[[item children] valueForKey:@"path"] componentsJoinedByString:@"\n"];
  653. } else if ([item bookmarkType] == SKBookmarkTypeFolder) {
  654. NSInteger count = [item countOfChildren];
  655. return count == 1 ? NSLocalizedString(@"1 item", @"Bookmark folder description") : [NSString stringWithFormat:NSLocalizedString(@"%ld items", @"Bookmark folder description"), (long)count];
  656. } else {
  657. return [[item fileURL] path];
  658. }
  659. } else if ([tcID isEqualToString:PAGE_COLUMNID]) {
  660. return [[item pageNumber] stringValue];
  661. }
  662. return @"";
  663. }
  664. - (void)outlineViewSelectionDidChange:(NSNotification *)notification {
  665. [self updateStatus];
  666. if ([QLPreviewPanel sharedPreviewPanelExists] && [[QLPreviewPanel sharedPreviewPanel] isVisible] && [[QLPreviewPanel sharedPreviewPanel] dataSource] == self)
  667. [[QLPreviewPanel sharedPreviewPanel] reloadData];
  668. }
  669. - (void)outlineView:(NSOutlineView *)ov deleteItems:(NSArray *)items {
  670. [self endEditing];
  671. for (SKBookmark *item in [minimumCoverForBookmarks(items) reverseObjectEnumerator]) {
  672. SKBookmark *parent = [item parent];
  673. NSUInteger itemIndex = [[parent children] indexOfObject:item];
  674. if (itemIndex != NSNotFound)
  675. [parent removeObjectFromChildrenAtIndex:itemIndex];
  676. }
  677. }
  678. - (BOOL)outlineView:(NSOutlineView *)ov canDeleteItems:(NSArray *)items {
  679. return [items count] > 0;
  680. }
  681. static void addBookmarkURLsToArray(NSArray *items, NSMutableArray *array) {
  682. for (SKBookmark *bm in items) {
  683. if ([bm bookmarkType] == SKBookmarkTypeBookmark) {
  684. NSURL *url = [bm fileURL];
  685. if (url)
  686. [array addObject:url];
  687. } else if ([bm bookmarkType] != SKBookmarkTypeSeparator) {
  688. addBookmarkURLsToArray([bm children], array);
  689. }
  690. }
  691. }
  692. - (void)outlineView:(NSOutlineView *)ov copyItems:(NSArray *)items {
  693. NSMutableArray *urls = [NSMutableArray array];
  694. addBookmarkURLsToArray(minimumCoverForBookmarks(items), urls);
  695. if ([urls count] > 0) {
  696. NSPasteboard *pboard = [NSPasteboard generalPasteboard];
  697. [pboard clearContents];
  698. [pboard writeObjects:urls];
  699. } else {
  700. NSBeep();
  701. }
  702. }
  703. - (BOOL)outlineView:(NSOutlineView *)ov canCopyItems:(NSArray *)items {
  704. return [items count] > 0;
  705. }
  706. - (void)outlineView:(NSOutlineView *)ov pasteFromPasteboard:(NSPasteboard *)pboard {
  707. NSArray *urls = [self readFileURLsFromPasteboard:pboard];
  708. if ([urls count] > 0) {
  709. NSArray *newBookmarks = [SKBookmark bookmarksForURLs:urls];
  710. if ([newBookmarks count] > 0) {
  711. SKBookmark *item = nil;
  712. NSUInteger anIndex = 0;
  713. [self getInsertionFolder:&item childIndex:&anIndex];
  714. NSMutableIndexSet *indexes = [NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(anIndex, [newBookmarks count])];
  715. [[item mutableArrayValueForKey:@"children"] insertObjects:newBookmarks atIndexes:indexes];
  716. if (item == _bookmarkRoot || [_outlineView isItemExpanded:item]) {
  717. if (item != _bookmarkRoot)
  718. [indexes shiftIndexesStartingAtIndex:0 by:[_outlineView rowForItem:item] + 1];
  719. [_outlineView selectRowIndexes:indexes byExtendingSelection:NO];
  720. }
  721. } else NSBeep();
  722. } else NSBeep();
  723. }
  724. - (BOOL)outlineView:(NSOutlineView *)ov canPasteFromPasteboard:(NSPasteboard *)pboard {
  725. return [self canReadFileURLFromPasteboard:pboard];
  726. }
  727. - (NSArray *)outlineView:(NSOutlineView *)ov typeSelectHelperSelectionStrings:(SKTypeSelectHelper *)typeSelectHelper {
  728. NSInteger i, count = [_outlineView numberOfRows];
  729. NSMutableArray *labels = [NSMutableArray arrayWithCapacity:count];
  730. for (i = 0; i < count; i++) {
  731. NSString *label = [[_outlineView itemAtRow:i] label];
  732. [labels addObject:label ?: @""];
  733. }
  734. return labels;
  735. }
  736. - (void)outlineView:(NSOutlineView *)ov typeSelectHelper:(SKTypeSelectHelper *)typeSelectHelper didFailToFindMatchForSearchString:(NSString *)searchString {
  737. // [statusBar setLeftStringValue:[NSString stringWithFormat:NSLocalizedString(@"No match: \"%@\"", @"Status message"), searchString]];
  738. }
  739. - (void)outlineView:(NSOutlineView *)ov typeSelectHelper:(SKTypeSelectHelper *)typeSelectHelper updateSearchString:(NSString *)searchString {
  740. // if (searchString)
  741. // [statusBar setLeftStringValue:[NSString stringWithFormat:NSLocalizedString(@"Finding: \"%@\"", @"Status message"), searchString]];
  742. // else
  743. // [self updateStatus];
  744. }
  745. #pragma mark Toolbar
  746. - (void)setupToolbar {
  747. // Create a new toolbar instance, and attach it to our document window
  748. NSToolbar *toolbar = [[NSToolbar alloc] initWithIdentifier:SKBookmarksToolbarIdentifier];
  749. SKToolbarItem *item;
  750. NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:3];
  751. // Set up toolbar properties: Allow customization, give a default display mode, and remember state in user defaults
  752. [toolbar setAllowsUserCustomization: YES];
  753. [toolbar setAutosavesConfiguration: YES];
  754. [toolbar setDisplayMode: NSToolbarDisplayModeDefault];
  755. // We are the delegate
  756. [toolbar setDelegate: self];
  757. // Add template toolbar items
  758. item = [[SKToolbarItem alloc] initWithItemIdentifier:SKBookmarksNewFolderToolbarItemIdentifier];
  759. [item setLabels:NSLocalizedString(@"New Folder", @"Toolbar item label")];
  760. [item setToolTip:NSLocalizedString(@"Add a New Folder", @"Tool tip message")];
  761. [item setImage:[NSImage imageNamed:SKImageNameNewFolder]];
  762. [item setTarget:self];
  763. [item setAction:@selector(insertBookmarkFolder:)];
  764. [dict setObject:item forKey:SKBookmarksNewFolderToolbarItemIdentifier];
  765. item = [[SKToolbarItem alloc] initWithItemIdentifier:SKBookmarksNewSeparatorToolbarItemIdentifier];
  766. [item setLabels:NSLocalizedString(@"New Separator", @"Toolbar item label")];
  767. [item setToolTip:NSLocalizedString(@"Add a New Separator", @"Tool tip message")];
  768. [item setImage:[NSImage imageNamed:SKImageNameNewSeparator]];
  769. [item setTarget:self];
  770. [item setAction:@selector(insertBookmarkSeparator:)];
  771. [dict setObject:item forKey:SKBookmarksNewSeparatorToolbarItemIdentifier];
  772. item = [[SKToolbarItem alloc] initWithItemIdentifier:SKBookmarksDeleteToolbarItemIdentifier];
  773. [item setLabels:NSLocalizedString(@"Delete", @"Toolbar item label")];
  774. [item setToolTip:NSLocalizedString(@"Delete Selected Items", @"Tool tip message")];
  775. [item setImage:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kToolbarDeleteIcon)]];
  776. [item setTarget:self];
  777. [item setAction:@selector(deleteBookmark:)];
  778. [dict setObject:item forKey:SKBookmarksDeleteToolbarItemIdentifier];
  779. toolbarItems = [dict mutableCopy];
  780. // Attach the toolbar to the window
  781. [[self window] setToolbar:toolbar];
  782. }
  783. - (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdent willBeInsertedIntoToolbar:(BOOL)willBeInserted {
  784. NSToolbarItem *item = [toolbarItems objectForKey:itemIdent];
  785. return item;
  786. }
  787. - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar {
  788. return [NSArray arrayWithObjects:
  789. SKBookmarksNewFolderToolbarItemIdentifier,
  790. SKBookmarksNewSeparatorToolbarItemIdentifier,
  791. SKBookmarksDeleteToolbarItemIdentifier, nil];
  792. }
  793. - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar {
  794. return [NSArray arrayWithObjects:
  795. SKBookmarksNewFolderToolbarItemIdentifier,
  796. SKBookmarksNewSeparatorToolbarItemIdentifier,
  797. SKBookmarksDeleteToolbarItemIdentifier,
  798. NSToolbarFlexibleSpaceItemIdentifier,
  799. NSToolbarSpaceItemIdentifier,
  800. NSToolbarSeparatorItemIdentifier,
  801. NSToolbarCustomizeToolbarItemIdentifier, nil];
  802. }
  803. - (BOOL)validateToolbarItem:(NSToolbarItem *)toolbarItem {
  804. if ([[[self window] toolbar] customizationPaletteIsRunning])
  805. return NO;
  806. else if ([[toolbarItem itemIdentifier] isEqualToString:SKBookmarksDeleteToolbarItemIdentifier])
  807. return [_outlineView canDelete];
  808. return YES;
  809. }
  810. - (BOOL)validateMenuItem:(NSMenuItem *)menuItem {
  811. if ([menuItem action] == @selector(toggleStatusBar:)) {
  812. // if ([statusBar isVisible])
  813. // [menuItem setTitle:NSLocalizedString(@"Hide Status Bar", @"Menu item title")];
  814. // else
  815. // [menuItem setTitle:NSLocalizedString(@"Show Status Bar", @"Menu item title")];
  816. return YES;
  817. } else if ([menuItem action] == @selector(addBookmark:)) {
  818. return [menuItem tag] == 0;
  819. }
  820. return YES;
  821. }
  822. #pragma mark Quick Look Panel Support
  823. - (BOOL)acceptsPreviewPanelControl:(QLPreviewPanel *)panel {
  824. return YES;
  825. }
  826. - (void)beginPreviewPanelControl:(QLPreviewPanel *)panel {
  827. [panel setDelegate:self];
  828. [panel setDataSource:self];
  829. }
  830. - (void)endPreviewPanelControl:(QLPreviewPanel *)panel {
  831. }
  832. - (NSArray *)previewItems {
  833. NSMutableArray *items = [NSMutableArray array];
  834. [[_outlineView selectedRowIndexes] enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
  835. SKBookmark *item = [_outlineView itemAtRow:idx];
  836. if ([item bookmarkType] == SKBookmarkTypeBookmark)
  837. [items addObject:item];
  838. else if ([item bookmarkType] == SKBookmarkTypeSession)
  839. [items addObjectsFromArray:[item children]];
  840. }];
  841. return items;
  842. }
  843. - (NSInteger)numberOfPreviewItemsInPreviewPanel:(QLPreviewPanel *)panel {
  844. return [[self previewItems] count];
  845. }
  846. - (id <QLPreviewItem>)previewPanel:(QLPreviewPanel *)panel previewItemAtIndex:(NSInteger)anIndex {
  847. return [[self previewItems] objectAtIndex:anIndex];
  848. }
  849. - (NSRect)previewPanel:(QLPreviewPanel *)panel sourceFrameOnScreenForPreviewItem:(id <QLPreviewItem>)item {
  850. if ([[(SKBookmark *)item parent] bookmarkType] == SKBookmarkTypeSession)
  851. item = [(SKBookmark *)item parent];
  852. NSInteger row = [_outlineView rowForItem:item];
  853. NSRect iconRect = NSZeroRect;
  854. if (item != nil && row != -1) {
  855. iconRect = [(SKTextWithIconCell *)[_outlineView preparedCellAtColumn:0 row:row] iconRectForBounds:[_outlineView frameOfCellAtColumn:0 row:row]];
  856. if (NSIntersectsRect([_outlineView visibleRect], iconRect)) {
  857. iconRect = [self convertRectToScreen:iconRect view:_outlineView];
  858. } else {
  859. iconRect = NSZeroRect;
  860. }
  861. }
  862. return iconRect;
  863. }
  864. - (NSRect)convertRectToScreen:(NSRect)rect view:(NSView *)view {
  865. rect = [view convertRect:rect toView:nil];
  866. rect.origin = [[self window] convertBaseToScreen:rect.origin];
  867. return rect;
  868. }
  869. - (NSImage *)previewPanel:(QLPreviewPanel *)panel transitionImageForPreviewItem:(id <QLPreviewItem>)item contentRect:(NSRect *)contentRect {
  870. if ([[(SKBookmark *)item parent] bookmarkType] == SKBookmarkTypeSession)
  871. item = [(SKBookmark *)item parent];
  872. return [(SKBookmark *)item icon];
  873. }
  874. - (BOOL)previewPanel:(QLPreviewPanel *)panel handleEvent:(NSEvent *)event {
  875. if ([event type] == NSKeyDown) {
  876. [_outlineView keyDown:event];
  877. return YES;
  878. }
  879. return NO;
  880. }
  881. #pragma mark -
  882. - (NSArray *)readFileURLsFromPasteboard:(NSPasteboard *)pboard {
  883. NSArray *fileURLs = [pboard readObjectsForClasses:[NSArray arrayWithObject:[NSURL class]] options:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSPasteboardURLReadingFileURLsOnlyKey, nil]];
  884. if ([fileURLs count] == 0 && [[pboard types] containsObject:NSFilenamesPboardType]) {
  885. NSArray *filenames = [pboard propertyListForType:NSFilenamesPboardType];
  886. if ([filenames count] > 0) {
  887. NSMutableArray *files = [NSMutableArray array];
  888. for (NSString *filename in filenames)
  889. [files addObject:[NSURL fileURLWithPath:[filename stringByExpandingTildeInPath]]];
  890. fileURLs = files;
  891. }
  892. }
  893. return fileURLs;
  894. }
  895. - (BOOL)canReadFileURLFromPasteboard:(NSPasteboard *)pboard {
  896. return [pboard canReadObjectForClasses:[NSArray arrayWithObject:[NSURL class]] options:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSPasteboardURLReadingFileURLsOnlyKey, nil]] ||
  897. [pboard canReadItemWithDataConformingToTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, nil]];
  898. }
  899. @end