BathyScapheで画像のインラインプレビューを可能にするプラグイン
Revision | baecdb0a8f194e13b23cda570e63a2209aa306cc (tree) |
---|---|
Time | 2012-05-18 01:42:45 |
Author | masakih <masakih@user...> |
Commiter | masakih |
[New] 自動読み込み
@@ -0,0 +1,28 @@ | ||
1 | +<?xml version="1.0" encoding="UTF-8"?> | |
2 | +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | |
3 | +<plist version="1.0"> | |
4 | +<dict> | |
5 | + <key>CFBundleDevelopmentRegion</key> | |
6 | + <string>English</string> | |
7 | + <key>CFBundleExecutable</key> | |
8 | + <string>${EXECUTABLE_NAME}</string> | |
9 | + <key>CFBundleIconFile</key> | |
10 | + <string></string> | |
11 | + <key>CFBundleIdentifier</key> | |
12 | + <string>com.masakih.BSInlinePreviewerEx</string> | |
13 | + <key>CFBundleInfoDictionaryVersion</key> | |
14 | + <string>6.0</string> | |
15 | + <key>CFBundleName</key> | |
16 | + <string>${PRODUCT_NAME}</string> | |
17 | + <key>CFBundlePackageType</key> | |
18 | + <string>BNDL</string> | |
19 | + <key>CFBundleShortVersionString</key> | |
20 | + <string>1.0b</string> | |
21 | + <key>CFBundleSignature</key> | |
22 | + <string>????</string> | |
23 | + <key>CFBundleVersion</key> | |
24 | + <string>1.0b</string> | |
25 | + <key>NSPrincipalClass</key> | |
26 | + <string>BSInlinePreviewerEx</string> | |
27 | +</dict> | |
28 | +</plist> |
@@ -0,0 +1,15 @@ | ||
1 | +// | |
2 | +// BSInlinePreviewerEx.h | |
3 | +// BSInlinePreviewer | |
4 | +// | |
5 | +// Created by 堀 昌樹 on 12/05/17. | |
6 | +// Copyright (c) 2012年 __MyCompanyName__. All rights reserved. | |
7 | +// | |
8 | + | |
9 | +#import "BSInlinePreviewer.h" | |
10 | + | |
11 | +@interface BSInlinePreviewerEx : BSInlinePreviewer | |
12 | +{ | |
13 | + NSMutableDictionary *tasking; | |
14 | +} | |
15 | +@end |
@@ -0,0 +1,143 @@ | ||
1 | +// | |
2 | +// BSInlinePreviewerEx.m | |
3 | +// BSInlinePreviewer | |
4 | +// | |
5 | +// Created by 堀 昌樹 on 12/05/17. | |
6 | +// Copyright (c) 2012年 __MyCompanyName__. All rights reserved. | |
7 | +// | |
8 | + | |
9 | +#import "BSInlinePreviewerEx.h" | |
10 | + | |
11 | +NSString *const CMRThreadViewerDidChangeThreadNotification = @"CMRThreadViewerDidChangeThreadNotification"; | |
12 | + | |
13 | +static NSString *const BSInlinePreviewerPreviewed = @"BSInlinePreviewerPreviewed"; | |
14 | + | |
15 | + | |
16 | +@interface BSInlinePreviewer(Private) | |
17 | +- (NSRange)linkRange; | |
18 | +- (NSImage *)downloadImageURL:(NSURL *)imageURL; | |
19 | +- (NSAttributedString *)attachmentAttributedStringWithImage:(NSImage *)image; | |
20 | +- (NSImage *)notFoundImage; | |
21 | +- (NSImage *)fitImage:(NSImage *)image toSize:(NSSize)targetSize; | |
22 | + | |
23 | +- (void)showProgressPanel; | |
24 | +- (void)closeProgressPanel; | |
25 | +@end | |
26 | + | |
27 | + | |
28 | +@implementation BSInlinePreviewerEx | |
29 | +// Designated Initializer | |
30 | +- (id)initWithPreferences:(AppDefaults *)prefs | |
31 | +{ | |
32 | + self = [super initWithPreferences:prefs]; | |
33 | + if(self) { | |
34 | + tasking = [[NSMutableDictionary alloc] init]; | |
35 | + NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; | |
36 | + [nc addObserver:self | |
37 | + selector:@selector(viewerDidEndFinishing:) | |
38 | + name:CMRThreadViewerDidChangeThreadNotification | |
39 | + object:nil]; | |
40 | + } | |
41 | + | |
42 | + return self; | |
43 | +} | |
44 | + | |
45 | +- (id)keyOfObject:(id)obj | |
46 | +{ | |
47 | + return [NSString stringWithFormat:@"%p", obj]; | |
48 | +} | |
49 | +- (void)insertImage:(NSDictionary *)attr | |
50 | +{ | |
51 | + NSTextView *tv = [attr objectForKey:@"View"]; | |
52 | + NSRange range = NSRangeFromString([attr objectForKey:@"Range"]); | |
53 | + id newInsertion = [attr objectForKey:@"Image"]; | |
54 | + NSUInteger offset = [[attr objectForKey:@"Offset"] unsignedIntegerValue]; | |
55 | + range.location += offset; | |
56 | + | |
57 | + NSTextStorage *ts = [tv textStorage]; | |
58 | + [ts beginEditing]; | |
59 | + { | |
60 | + [ts addAttribute:BSInlinePreviewerPreviewed | |
61 | + value:[NSNumber numberWithBool:YES] | |
62 | + range:range]; | |
63 | + | |
64 | + [ts insertAttributedString:newInsertion atIndex:range.location]; | |
65 | + } | |
66 | + [ts endEditing]; | |
67 | +} | |
68 | +- (void)viewerDidEndFinishing:(id)no | |
69 | +{ | |
70 | + NSLog(@"CMRThreadViewerDidChangeThreadNotification %@", no); | |
71 | + id threadViewer = [no object]; | |
72 | + NSTextView *tv = [[threadViewer textView] retain]; | |
73 | + @synchronized(tasking) { | |
74 | + id check = [tasking objectForKey:[self keyOfObject:tv]]; | |
75 | + if(check) return; | |
76 | + [tasking setObject:tv forKey:[self keyOfObject:tv]]; | |
77 | + } | |
78 | + | |
79 | + dispatch_async(dispatch_get_global_queue(0,0), ^{ | |
80 | + NSUInteger i = 0; | |
81 | + while([[tv textStorage] length] == 0) { | |
82 | + i++; | |
83 | + if(i > NSUIntegerMax - 5) { | |
84 | + NSLog(@"Abort"); | |
85 | + return; | |
86 | + } | |
87 | + } | |
88 | + sleep(1); | |
89 | + NSTextStorage *ts = [[tv textStorage] copy]; | |
90 | + | |
91 | + NSMutableArray *links = [NSMutableArray array]; | |
92 | + | |
93 | + [ts enumerateAttribute:NSLinkAttributeName | |
94 | + inRange:NSMakeRange(0, [ts length]) | |
95 | + options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired | |
96 | + usingBlock:^(id value, NSRange range, BOOL *stop) { | |
97 | + NSURL *url = [NSURL URLWithString:value]; | |
98 | + if([self validateLink:url]) { | |
99 | + [links addObject:[NSDictionary dictionaryWithObjectsAndKeys:url, @"Link", | |
100 | + NSStringFromRange(range), @"Range", nil]]; | |
101 | + } | |
102 | + }]; | |
103 | + | |
104 | + __block NSUInteger offset = 0; | |
105 | +// NSUInteger index = 0; | |
106 | + NSUInteger count = [links count]; | |
107 | +// for(index = 0; index < count; index++) { | |
108 | + dispatch_apply(count, dispatch_get_global_queue(0,0), ^(size_t index){ | |
109 | + id dict = [links objectAtIndex:index]; | |
110 | + NSRange range = NSRangeFromString([dict objectForKey:@"Range"]); | |
111 | + if([ts attribute:BSInlinePreviewerPreviewed atIndex:range.location longestEffectiveRange:NULL inRange:range]) { | |
112 | + return; | |
113 | + } | |
114 | + // download image. | |
115 | + self.totalDownloads = self.remainder = 1; | |
116 | + NSImage *image = [self downloadImageURL:[dict objectForKey:@"Link"]]; | |
117 | + self.remainder = 0; | |
118 | + if(!image) return; | |
119 | + | |
120 | + id newInsertion = [self attachmentAttributedStringWithImage:image]; | |
121 | + | |
122 | + | |
123 | + NSDictionary *attr = [[NSDictionary alloc] initWithObjectsAndKeys: | |
124 | + newInsertion, @"Image", | |
125 | + [dict objectForKey:@"Range"], @"Range", | |
126 | + tv, @"View", | |
127 | + [NSNumber numberWithUnsignedInteger:offset], @"Offset", | |
128 | + nil]; | |
129 | + [self performSelectorOnMainThread:@selector(insertImage:) withObject:attr waitUntilDone:NO]; | |
130 | + offset += [newInsertion length]; | |
131 | + [attr release]; | |
132 | + }); | |
133 | + [ts release]; | |
134 | + [tv release]; | |
135 | + @synchronized(tasking) { | |
136 | + [tasking removeObjectForKey:[self keyOfObject:tv]]; | |
137 | + } | |
138 | +// NSLog(@"ts \n%@", ts); | |
139 | + }); | |
140 | +} | |
141 | + | |
142 | + | |
143 | +@end |
@@ -0,0 +1,11 @@ | ||
1 | +/* | |
2 | + InfoPlist.strings | |
3 | + BSInlinePreviewer | |
4 | + | |
5 | + Created by 堀 昌樹 on 12/05/17. | |
6 | + Copyright (c) 2012年 __MyCompanyName__. All rights reserved. | |
7 | +*/ | |
8 | + | |
9 | +BSPreviewerDisplayName = "BSInlinePreviewerEx"; | |
10 | +CFBundleGetInfoString = "BSInlinePreviewerEx 1.0b"; | |
11 | +NSHumanReadableCopyright = "© masakih, 2012"; |
@@ -0,0 +1,11 @@ | ||
1 | +/* | |
2 | + InfoPlist.strings | |
3 | + BSInlinePreviewer | |
4 | + | |
5 | + Created by 堀 昌樹 on 12/05/17. | |
6 | + Copyright (c) 2012年 __MyCompanyName__. All rights reserved. | |
7 | +*/ | |
8 | + | |
9 | +BSPreviewerDisplayName = "BSInlinePreviewerEx"; | |
10 | +CFBundleGetInfoString = "BSInlinePreviewerEx 1.0b"; | |
11 | +NSHumanReadableCopyright = "© masakih, 2012"; |