|
boredzo@2402
|
1 |
//
|
|
boredzo@2402
|
2 |
// GrowlWebKitWindowController.m
|
|
boredzo@2402
|
3 |
// Growl
|
|
boredzo@2402
|
4 |
//
|
|
boredzo@2402
|
5 |
// Created by Ingmar Stein on Thu Apr 14 2005.
|
|
ingmarstein@3040
|
6 |
// Copyright 2005-2006 The Growl Project. All rights reserved.
|
|
boredzo@2402
|
7 |
//
|
|
boredzo@2402
|
8 |
|
|
boredzo@2402
|
9 |
#import "GrowlWebKitWindowController.h"
|
|
boredzo@2402
|
10 |
#import "GrowlWebKitWindowView.h"
|
|
boredzo@2402
|
11 |
#import "GrowlWebKitPrefsController.h"
|
|
boredzo@2402
|
12 |
#import "GrowlWebKitDefines.h"
|
|
boredzo@2402
|
13 |
#import "NSWindow+Transforms.h"
|
|
boredzo@2402
|
14 |
#import "GrowlPluginController.h"
|
|
boredzo@2429
|
15 |
#import "NSViewAdditions.h"
|
|
boredzo@2402
|
16 |
#import "GrowlDefines.h"
|
|
ingmarstein@2470
|
17 |
#import "GrowlPathUtilities.h"
|
|
ingmarstein@2699
|
18 |
#import "GrowlApplicationNotification.h"
|
|
ingmarstein@2632
|
19 |
#include "CFGrowlAdditions.h"
|
|
ingmarstein@2641
|
20 |
#include "CFDictionaryAdditions.h"
|
|
ingmarstein@2627
|
21 |
#include "CFMutableStringAdditions.h"
|
|
eridius@2773
|
22 |
#import "GrowlNotificationDisplayBridge.h"
|
|
eridius@2773
|
23 |
#import "GrowlDisplayPlugin.h"
|
|
ingmarstein@3037
|
24 |
#import "GrowlFadingWindowTransition.h"
|
|
boredzo@2402
|
25 |
|
|
ingmarstein@2711
|
26 |
/*
|
|
ingmarstein@2711
|
27 |
* A panel that always pretends to be the key window.
|
|
ingmarstein@2711
|
28 |
*/
|
|
ingmarstein@2711
|
29 |
@interface KeyPanel : NSPanel {
|
|
boredzo@2402
|
30 |
}
|
|
boredzo@2402
|
31 |
@end
|
|
boredzo@2402
|
32 |
|
|
ingmarstein@2711
|
33 |
@implementation KeyPanel
|
|
ingmarstein@2711
|
34 |
- (BOOL) isKeyWindow {
|
|
boredzo@2402
|
35 |
return YES;
|
|
boredzo@2402
|
36 |
}
|
|
boredzo@2402
|
37 |
@end
|
|
boredzo@2402
|
38 |
|
|
evands@4035
|
39 |
@interface NSData (Base64Additions)
|
|
evands@4035
|
40 |
- (NSString *)base64Encoding;
|
|
evands@4035
|
41 |
@end
|
|
evands@4035
|
42 |
|
|
evands@4035
|
43 |
@interface NSImage (PNGRepAddition)
|
|
evands@4035
|
44 |
- (NSData *)PNGRepresentation;
|
|
evands@4035
|
45 |
@end
|
|
evands@4035
|
46 |
|
|
boredzo@2402
|
47 |
@implementation GrowlWebKitWindowController
|
|
boredzo@2402
|
48 |
|
|
evands@3636
|
49 |
#define GrowlWebKitDurationPrefDefault 4.0
|
|
boredzo@2402
|
50 |
#define ADDITIONAL_LINES_DISPLAY_TIME 0.5
|
|
boredzo@2402
|
51 |
#define MAX_DISPLAY_TIME 10.0
|
|
boredzo@2402
|
52 |
#define GrowlWebKitPadding 5.0f
|
|
boredzo@2402
|
53 |
|
|
boredzo@2402
|
54 |
#pragma mark -
|
|
boredzo@2402
|
55 |
|
|
eridius@2773
|
56 |
- (id) initWithBridge:(GrowlNotificationDisplayBridge *)displayBridge {
|
|
eridius@2773
|
57 |
// init the window used to init
|
|
ingmarstein@2711
|
58 |
NSPanel *panel = [[KeyPanel alloc] initWithContentRect:NSMakeRect(0.0f, 0.0f, 270.0f, 1.0f)
|
|
ingmarstein@2711
|
59 |
styleMask:NSBorderlessWindowMask | NSNonactivatingPanelMask
|
|
ingmarstein@2711
|
60 |
backing:NSBackingStoreBuffered
|
|
rudy@3005
|
61 |
defer:YES];
|
|
ingmarstein@3036
|
62 |
if (!(self = [super initWithWindow:panel]))
|
|
eridius@2773
|
63 |
return nil;
|
|
ingmarstein@2943
|
64 |
|
|
ingmarstein@3036
|
65 |
GrowlDisplayPlugin *plugin = [displayBridge display];
|
|
ingmarstein@3036
|
66 |
|
|
eridius@2773
|
67 |
// Read the template file....exit on error...
|
|
eridius@2773
|
68 |
NSError *error = nil;
|
|
ingmarstein@3036
|
69 |
NSBundle *displayBundle = [plugin bundle];
|
|
ingmarstein@3035
|
70 |
NSString *templateFile = [displayBundle pathForResource:@"template" ofType:@"html"];
|
|
ingmarstein@3036
|
71 |
if (![[NSFileManager defaultManager] fileExistsAtPath:templateFile])
|
|
ingmarstein@3036
|
72 |
templateFile = [[NSBundle mainBundle] pathForResource:@"template" ofType:@"html"];
|
|
eridius@2773
|
73 |
templateHTML = [[NSString alloc] initWithContentsOfFile:templateFile
|
|
eridius@2773
|
74 |
encoding:NSUTF8StringEncoding
|
|
eridius@2773
|
75 |
error:&error];
|
|
ingmarstein@2943
|
76 |
if (!templateHTML) {
|
|
eridius@2773
|
77 |
NSLog(@"ERROR: could not read template '%@' - %@", templateFile,error);
|
|
eridius@2773
|
78 |
[self release];
|
|
eridius@2773
|
79 |
return nil;
|
|
eridius@2773
|
80 |
}
|
|
evands@3576
|
81 |
baseURL = [[NSURL fileURLWithPath:[displayBundle resourcePath]] retain];
|
|
ingmarstein@2943
|
82 |
|
|
eridius@2773
|
83 |
// Read the prefs for the plugin...
|
|
eridius@2773
|
84 |
unsigned theScreenNo = 0U;
|
|
eridius@2773
|
85 |
READ_GROWL_PREF_INT(GrowlWebKitScreenPref, [plugin prefDomain], &theScreenNo);
|
|
eridius@2773
|
86 |
[self setScreenNumber:theScreenNo];
|
|
ingmarstein@2943
|
87 |
|
|
evands@3636
|
88 |
CFNumberRef prefsDuration = NULL;
|
|
evands@3638
|
89 |
READ_GROWL_PREF_VALUE(GrowlWebKitDurationPref, [plugin prefDomain], CFNumberRef, &prefsDuration);
|
|
evands@3636
|
90 |
[self setDisplayDuration:(prefsDuration ?
|
|
evands@3636
|
91 |
[(NSNumber *)prefsDuration doubleValue] :
|
|
evands@3636
|
92 |
GrowlWebKitDurationPrefDefault)];
|
|
boredzo@4135
|
93 |
if (prefsDuration) CFRelease(prefsDuration);
|
|
evands@3636
|
94 |
|
|
eridius@2773
|
95 |
// Read the plugin specifics from the info.plist
|
|
eridius@2773
|
96 |
NSDictionary *styleInfo = [[plugin bundle] infoDictionary];
|
|
eridius@2773
|
97 |
BOOL hasShadow = NO;
|
|
eridius@2773
|
98 |
hasShadow = [(NSNumber *)[styleInfo valueForKey:@"GrowlHasShadow"] boolValue];
|
|
eridius@2773
|
99 |
paddingX = GrowlWebKitPadding;
|
|
eridius@2773
|
100 |
paddingY = GrowlWebKitPadding;
|
|
eridius@2773
|
101 |
NSNumber *xPad = [styleInfo valueForKey:@"GrowlPaddingX"];
|
|
eridius@2773
|
102 |
NSNumber *yPad = [styleInfo valueForKey:@"GrowlPaddingY"];
|
|
eridius@2773
|
103 |
if (xPad)
|
|
eridius@2773
|
104 |
paddingX = [xPad floatValue];
|
|
eridius@2773
|
105 |
if (yPad)
|
|
eridius@2773
|
106 |
paddingY = [yPad floatValue];
|
|
ingmarstein@2943
|
107 |
|
|
eridius@2773
|
108 |
// Configure the window
|
|
boredzo@2402
|
109 |
[panel setBecomesKeyOnlyIfNeeded:YES];
|
|
boredzo@2402
|
110 |
[panel setHidesOnDeactivate:NO];
|
|
boredzo@2402
|
111 |
[panel setBackgroundColor:[NSColor clearColor]];
|
|
boredzo@2402
|
112 |
[panel setLevel:NSStatusWindowLevel];
|
|
boredzo@2402
|
113 |
[panel setSticky:YES];
|
|
boredzo@2402
|
114 |
[panel setAlphaValue:0.0f];
|
|
boredzo@2402
|
115 |
[panel setOpaque:NO];
|
|
boredzo@2402
|
116 |
[panel setCanHide:NO];
|
|
boredzo@2402
|
117 |
[panel setOneShot:YES];
|
|
boredzo@2402
|
118 |
[panel useOptimizedDrawing:YES];
|
|
boredzo@2402
|
119 |
[panel disableCursorRects];
|
|
eridius@2773
|
120 |
[panel setHasShadow:hasShadow];
|
|
evands@3502
|
121 |
[panel setDelegate:self];
|
|
ingmarstein@2943
|
122 |
|
|
eridius@2773
|
123 |
// Configure the view
|
|
eridius@2773
|
124 |
NSRect panelFrame = [panel frame];
|
|
boredzo@2402
|
125 |
GrowlWebKitWindowView *view = [[GrowlWebKitWindowView alloc] initWithFrame:panelFrame
|
|
boredzo@2402
|
126 |
frameName:nil
|
|
boredzo@2402
|
127 |
groupName:nil];
|
|
boredzo@2402
|
128 |
[view setMaintainsBackForwardList:NO];
|
|
boredzo@2402
|
129 |
[view setTarget:self];
|
|
boredzo@2402
|
130 |
[view setAction:@selector(notificationClicked:)];
|
|
boredzo@2402
|
131 |
[view setPolicyDelegate:self];
|
|
boredzo@2402
|
132 |
[view setFrameLoadDelegate:self];
|
|
boredzo@2402
|
133 |
if ([view respondsToSelector:@selector(setDrawsBackground:)])
|
|
boredzo@2402
|
134 |
[view setDrawsBackground:NO];
|
|
boredzo@2402
|
135 |
[panel setContentView:view];
|
|
ingmarstein@2711
|
136 |
[panel makeFirstResponder:[[[view mainFrame] frameView] documentView]];
|
|
evands@4032
|
137 |
[view release];
|
|
evands@4032
|
138 |
|
|
ingmarstein@3036
|
139 |
[self setBridge:displayBridge];
|
|
ingmarstein@2943
|
140 |
|
|
ingmarstein@3037
|
141 |
// set up the transitions...
|
|
ingmarstein@3037
|
142 |
GrowlFadingWindowTransition *fader = [[GrowlFadingWindowTransition alloc] initWithWindow:panel];
|
|
ingmarstein@3037
|
143 |
[self addTransition:fader];
|
|
ingmarstein@3037
|
144 |
[self setStartPercentage:0 endPercentage:100 forTransition:fader];
|
|
ingmarstein@3037
|
145 |
[fader setAutoReverses:YES];
|
|
ingmarstein@3037
|
146 |
[fader release];
|
|
evands@4032
|
147 |
|
|
evands@3543
|
148 |
[panel release];
|
|
evands@3543
|
149 |
|
|
boredzo@2402
|
150 |
return self;
|
|
boredzo@2402
|
151 |
}
|
|
boredzo@2402
|
152 |
|
|
boredzo@2411
|
153 |
- (void) dealloc {
|
|
evands@3645
|
154 |
GrowlWebKitWindowView *webView = [[self window] contentView];
|
|
eridius@2773
|
155 |
[webView setPolicyDelegate:nil];
|
|
eridius@2773
|
156 |
[webView setFrameLoadDelegate:nil];
|
|
evands@3645
|
157 |
[webView setTarget:nil];
|
|
evands@4034
|
158 |
|
|
eridius@2773
|
159 |
[templateHTML release];
|
|
evands@3576
|
160 |
[baseURL release];
|
|
evands@3576
|
161 |
|
|
boredzo@2411
|
162 |
[super dealloc];
|
|
boredzo@2411
|
163 |
}
|
|
boredzo@2411
|
164 |
|
|
bgannin@3404
|
165 |
- (void) setTitle:(NSString *)title text:(NSString *)text icon:(NSImage *)icon priority:(int)priority forView:(WebView *)view {
|
|
ingmarstein@2627
|
166 |
CFStringRef priorityName;
|
|
boredzo@2402
|
167 |
switch (priority) {
|
|
boredzo@2402
|
168 |
case -2:
|
|
ingmarstein@2627
|
169 |
priorityName = CFSTR("verylow");
|
|
boredzo@2402
|
170 |
break;
|
|
boredzo@2402
|
171 |
case -1:
|
|
ingmarstein@2627
|
172 |
priorityName = CFSTR("moderate");
|
|
boredzo@2402
|
173 |
break;
|
|
boredzo@2402
|
174 |
default:
|
|
boredzo@2402
|
175 |
case 0:
|
|
ingmarstein@2627
|
176 |
priorityName = CFSTR("normal");
|
|
boredzo@2402
|
177 |
break;
|
|
boredzo@2402
|
178 |
case 1:
|
|
ingmarstein@2627
|
179 |
priorityName = CFSTR("high");
|
|
boredzo@2402
|
180 |
break;
|
|
boredzo@2402
|
181 |
case 2:
|
|
ingmarstein@2627
|
182 |
priorityName = CFSTR("emergency");
|
|
boredzo@2402
|
183 |
break;
|
|
boredzo@2402
|
184 |
}
|
|
ingmarstein@2943
|
185 |
|
|
eridius@2773
|
186 |
CFMutableStringRef htmlString = CFStringCreateMutableCopy(kCFAllocatorDefault, 0, (CFStringRef)templateHTML);
|
|
boredzo@4063
|
187 |
|
|
boredzo@4063
|
188 |
NSString *imageMediaType = @"image/png";
|
|
boredzo@4063
|
189 |
NSData *imageData = [icon PNGRepresentation];
|
|
boredzo@4063
|
190 |
if (!imageData) {
|
|
boredzo@4063
|
191 |
//Couldn't create a PNG, so fall back on TIFF.
|
|
boredzo@4063
|
192 |
imageMediaType = @"image/tiff";
|
|
boredzo@4063
|
193 |
imageData = [icon TIFFRepresentation];
|
|
boredzo@4063
|
194 |
}
|
|
boredzo@4063
|
195 |
NSString *growlImageString = [NSString stringWithFormat:@"data:%@;base64,%@", imageMediaType, [imageData base64Encoding]];
|
|
boredzo@2402
|
196 |
|
|
boredzo@2402
|
197 |
float opacity = 95.0f;
|
|
eridius@2773
|
198 |
READ_GROWL_PREF_FLOAT(GrowlWebKitOpacityPref, [[bridge display] prefDomain], &opacity);
|
|
boredzo@2402
|
199 |
opacity *= 0.01f;
|
|
ingmarstein@2627
|
200 |
|
|
boredzo@3655
|
201 |
CFStringRef titleHTML = createStringByEscapingForHTML((CFStringRef)title);
|
|
boredzo@3655
|
202 |
CFStringRef textHTML = createStringByEscapingForHTML((CFStringRef)text);
|
|
ingmarstein@2627
|
203 |
CFStringRef opacityString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%f"), opacity);
|
|
ingmarstein@2627
|
204 |
|
|
evands@3576
|
205 |
CFStringFindAndReplace(htmlString, CFSTR("%baseurl%"), (CFStringRef)[baseURL absoluteString], CFRangeMake(0, CFStringGetLength(htmlString)), 0);
|
|
evands@3576
|
206 |
CFStringFindAndReplace(htmlString, CFSTR("%opacity%"), opacityString, CFRangeMake(0, CFStringGetLength(htmlString)), 0);
|
|
evands@3576
|
207 |
CFStringFindAndReplace(htmlString, CFSTR("%priority%"), priorityName, CFRangeMake(0, CFStringGetLength(htmlString)), 0);
|
|
evands@4035
|
208 |
CFStringFindAndReplace(htmlString, CFSTR("growlimage://%image%"), (CFStringRef)growlImageString, CFRangeMake(0, CFStringGetLength(htmlString)), 0);
|
|
boredzo@3655
|
209 |
CFStringFindAndReplace(htmlString, CFSTR("%title%"), (CFStringRef)titleHTML, CFRangeMake(0, CFStringGetLength(htmlString)), 0);
|
|
boredzo@3655
|
210 |
CFStringFindAndReplace(htmlString, CFSTR("%text%"), (CFStringRef)textHTML, CFRangeMake(0, CFStringGetLength(htmlString)), 0);
|
|
ingmarstein@2627
|
211 |
|
|
ingmarstein@2627
|
212 |
CFRelease(opacityString);
|
|
boredzo@3655
|
213 |
CFRelease(titleHTML);
|
|
boredzo@3655
|
214 |
CFRelease(textHTML);
|
|
boredzo@2402
|
215 |
WebFrame *webFrame = [view mainFrame];
|
|
boredzo@2402
|
216 |
[[self window] disableFlushWindow];
|
|
evands@3502
|
217 |
|
|
ingmarstein@2652
|
218 |
[webFrame loadHTMLString:(NSString *)htmlString baseURL:nil];
|
|
boredzo@2402
|
219 |
[[webFrame frameView] setAllowsScrolling:NO];
|
|
ingmarstein@2652
|
220 |
CFRelease(htmlString);
|
|
boredzo@2402
|
221 |
}
|
|
boredzo@2402
|
222 |
|
|
boredzo@2402
|
223 |
/*!
|
|
boredzo@2402
|
224 |
* @brief Prevent the webview from following external links. We direct these to the users web browser.
|
|
boredzo@2402
|
225 |
*/
|
|
boredzo@2402
|
226 |
- (void) webView:(WebView *)sender
|
|
boredzo@2402
|
227 |
decidePolicyForNavigationAction:(NSDictionary *)actionInformation
|
|
boredzo@2402
|
228 |
request:(NSURLRequest *)request
|
|
boredzo@2402
|
229 |
frame:(WebFrame *)frame
|
|
boredzo@2402
|
230 |
decisionListener:(id<WebPolicyDecisionListener>)listener
|
|
boredzo@2402
|
231 |
{
|
|
boredzo@2402
|
232 |
#pragma unused(sender, request, frame)
|
|
ingmarstein@2641
|
233 |
int actionKey = getIntegerForKey(actionInformation, WebActionNavigationTypeKey);
|
|
boredzo@2402
|
234 |
if (actionKey == WebNavigationTypeOther) {
|
|
boredzo@2402
|
235 |
[listener use];
|
|
boredzo@2402
|
236 |
} else {
|
|
ingmarstein@2641
|
237 |
NSURL *url = getObjectForKey(actionInformation, WebActionOriginalURLKey);
|
|
boredzo@2402
|
238 |
|
|
boredzo@2402
|
239 |
//Ignore file URLs, but open anything else
|
|
boredzo@2402
|
240 |
if (![url isFileURL])
|
|
boredzo@2402
|
241 |
[[NSWorkspace sharedWorkspace] openURL:url];
|
|
boredzo@2402
|
242 |
|
|
boredzo@2402
|
243 |
[listener ignore];
|
|
boredzo@2402
|
244 |
}
|
|
boredzo@2402
|
245 |
}
|
|
boredzo@2402
|
246 |
|
|
boredzo@2402
|
247 |
/*!
|
|
boredzo@2402
|
248 |
* @brief Invoked once the webview has loaded and is ready to accept content
|
|
boredzo@2402
|
249 |
*/
|
|
boredzo@2402
|
250 |
- (void) webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame {
|
|
boredzo@2402
|
251 |
#pragma unused(frame)
|
|
boredzo@2402
|
252 |
NSWindow *myWindow = [self window];
|
|
boredzo@2402
|
253 |
if ([myWindow isFlushWindowDisabled])
|
|
boredzo@2402
|
254 |
[myWindow enableFlushWindow];
|
|
boredzo@2402
|
255 |
|
|
boredzo@2402
|
256 |
GrowlWebKitWindowView *view = (GrowlWebKitWindowView *)sender;
|
|
boredzo@2402
|
257 |
[view sizeToFit];
|
|
evands@3546
|
258 |
|
|
evands@3576
|
259 |
//Update our new frame
|
|
evands@3576
|
260 |
[[GrowlPositionController sharedInstance] positionDisplay:self];
|
|
evands@3576
|
261 |
|
|
ingmarstein@2580
|
262 |
[myWindow invalidateShadow];
|
|
boredzo@2402
|
263 |
}
|
|
boredzo@2402
|
264 |
|
|
ingmarstein@2943
|
265 |
- (void) setNotification:(GrowlApplicationNotification *)theNotification {
|
|
eridius@2773
|
266 |
if (notification == theNotification)
|
|
eridius@2773
|
267 |
return;
|
|
ingmarstein@2943
|
268 |
|
|
ingmarstein@3036
|
269 |
[super setNotification:theNotification];
|
|
ingmarstein@3036
|
270 |
|
|
eridius@2773
|
271 |
// Extract the new details from the notification
|
|
eridius@2773
|
272 |
NSDictionary *noteDict = [notification dictionaryRepresentation];
|
|
bgannin@3404
|
273 |
NSString *title = [notification title];
|
|
bgannin@3404
|
274 |
NSString *text = [notification notificationDescription];
|
|
eridius@2773
|
275 |
NSImage *icon = getObjectForKey(noteDict, GROWL_NOTIFICATION_ICON);
|
|
eridius@2773
|
276 |
int priority = getIntegerForKey(noteDict, GROWL_NOTIFICATION_PRIORITY);
|
|
ingmarstein@2943
|
277 |
|
|
eridius@2773
|
278 |
NSPanel *panel = (NSPanel *)[self window];
|
|
eridius@2773
|
279 |
WebView *view = [panel contentView];
|
|
bgannin@3404
|
280 |
[self setTitle:title text:text icon:icon priority:priority forView:view];
|
|
ingmarstein@2943
|
281 |
|
|
evands@3576
|
282 |
// NSRect panelFrame = [view frame];
|
|
evands@3576
|
283 |
|
|
evands@3576
|
284 |
// [panel setFrame:panelFrame display:NO];
|
|
eridius@2773
|
285 |
}
|
|
eridius@2773
|
286 |
|
|
ingmarstein@3037
|
287 |
#pragma mark -
|
|
ingmarstein@3037
|
288 |
#pragma mark positioning methods
|
|
ingmarstein@3037
|
289 |
|
|
ingmarstein@3037
|
290 |
- (NSPoint) idealOriginInRect:(NSRect)rect {
|
|
ingmarstein@3037
|
291 |
NSRect viewFrame = [[[self window] contentView] frame];
|
|
bgannin@3317
|
292 |
enum GrowlPosition originatingPosition = [[GrowlPositionController sharedInstance] originPosition];
|
|
bgannin@3273
|
293 |
NSPoint idealOrigin;
|
|
evands@4032
|
294 |
|
|
bgannin@3273
|
295 |
switch(originatingPosition){
|
|
bgannin@3273
|
296 |
case GrowlTopRightPosition:
|
|
bgannin@3273
|
297 |
idealOrigin = NSMakePoint(NSMaxX(rect) - NSWidth(viewFrame) - paddingX,
|
|
bgannin@3273
|
298 |
NSMaxY(rect) - paddingY - NSHeight(viewFrame));
|
|
bgannin@3273
|
299 |
break;
|
|
bgannin@3273
|
300 |
case GrowlTopLeftPosition:
|
|
bgannin@3273
|
301 |
idealOrigin = NSMakePoint(NSMinX(rect) + paddingX,
|
|
bgannin@3273
|
302 |
NSMaxY(rect) - paddingY - NSHeight(viewFrame));
|
|
bgannin@3273
|
303 |
break;
|
|
bgannin@3273
|
304 |
case GrowlBottomLeftPosition:
|
|
bgannin@3273
|
305 |
idealOrigin = NSMakePoint(NSMinX(rect) + paddingX,
|
|
bgannin@3273
|
306 |
NSMinY(rect) + paddingY);
|
|
bgannin@3273
|
307 |
break;
|
|
bgannin@3273
|
308 |
case GrowlBottomRightPosition:
|
|
bgannin@3273
|
309 |
idealOrigin = NSMakePoint(NSMaxX(rect) - NSWidth(viewFrame) - paddingX,
|
|
bgannin@3273
|
310 |
NSMinY(rect) + paddingY);
|
|
bgannin@3273
|
311 |
break;
|
|
bgannin@3273
|
312 |
default:
|
|
bgannin@3273
|
313 |
idealOrigin = NSMakePoint(NSMaxX(rect) - NSWidth(viewFrame) - paddingX,
|
|
bgannin@3273
|
314 |
NSMaxY(rect) - paddingY - NSHeight(viewFrame));
|
|
bgannin@3273
|
315 |
break;
|
|
bgannin@3273
|
316 |
}
|
|
evands@4032
|
317 |
|
|
bgannin@3273
|
318 |
return idealOrigin;
|
|
ingmarstein@3037
|
319 |
}
|
|
ingmarstein@3037
|
320 |
|
|
boredzo@3095
|
321 |
- (enum GrowlExpansionDirection) primaryExpansionDirection {
|
|
bgannin@3317
|
322 |
enum GrowlPosition originatingPosition = [[GrowlPositionController sharedInstance] originPosition];
|
|
bgannin@3273
|
323 |
enum GrowlExpansionDirection directionToExpand;
|
|
bgannin@3273
|
324 |
|
|
bgannin@3273
|
325 |
switch(originatingPosition){
|
|
bgannin@3273
|
326 |
case GrowlTopLeftPosition:
|
|
bgannin@3273
|
327 |
directionToExpand = GrowlDownExpansionDirection;
|
|
bgannin@3273
|
328 |
break;
|
|
bgannin@3273
|
329 |
case GrowlTopRightPosition:
|
|
bgannin@3273
|
330 |
directionToExpand = GrowlDownExpansionDirection;
|
|
bgannin@3273
|
331 |
break;
|
|
bgannin@3273
|
332 |
case GrowlBottomLeftPosition:
|
|
bgannin@3273
|
333 |
directionToExpand = GrowlUpExpansionDirection;
|
|
bgannin@3273
|
334 |
break;
|
|
bgannin@3273
|
335 |
case GrowlBottomRightPosition:
|
|
bgannin@3273
|
336 |
directionToExpand = GrowlUpExpansionDirection;
|
|
bgannin@3273
|
337 |
break;
|
|
bgannin@3273
|
338 |
default:
|
|
bgannin@3273
|
339 |
directionToExpand = GrowlDownExpansionDirection;
|
|
bgannin@3273
|
340 |
break;
|
|
bgannin@3273
|
341 |
}
|
|
bgannin@3273
|
342 |
|
|
bgannin@3273
|
343 |
return directionToExpand;
|
|
ingmarstein@3037
|
344 |
}
|
|
ingmarstein@3037
|
345 |
|
|
boredzo@3095
|
346 |
- (enum GrowlExpansionDirection) secondaryExpansionDirection {
|
|
bgannin@3317
|
347 |
enum GrowlPosition originatingPosition = [[GrowlPositionController sharedInstance] originPosition];
|
|
bgannin@3273
|
348 |
enum GrowlExpansionDirection directionToExpand;
|
|
bgannin@3273
|
349 |
|
|
bgannin@3273
|
350 |
switch(originatingPosition){
|
|
bgannin@3273
|
351 |
case GrowlTopLeftPosition:
|
|
bgannin@3273
|
352 |
directionToExpand = GrowlRightExpansionDirection;
|
|
bgannin@3273
|
353 |
break;
|
|
bgannin@3273
|
354 |
case GrowlTopRightPosition:
|
|
bgannin@3273
|
355 |
directionToExpand = GrowlLeftExpansionDirection;
|
|
bgannin@3273
|
356 |
break;
|
|
bgannin@3273
|
357 |
case GrowlBottomLeftPosition:
|
|
bgannin@3273
|
358 |
directionToExpand = GrowlRightExpansionDirection;
|
|
bgannin@3273
|
359 |
break;
|
|
bgannin@3273
|
360 |
case GrowlBottomRightPosition:
|
|
bgannin@3273
|
361 |
directionToExpand = GrowlLeftExpansionDirection;
|
|
bgannin@3273
|
362 |
break;
|
|
bgannin@3273
|
363 |
default:
|
|
bgannin@3273
|
364 |
directionToExpand = GrowlRightExpansionDirection;
|
|
bgannin@3273
|
365 |
break;
|
|
bgannin@3273
|
366 |
}
|
|
bgannin@3273
|
367 |
|
|
bgannin@3273
|
368 |
return directionToExpand;
|
|
ingmarstein@3037
|
369 |
}
|
|
ingmarstein@3037
|
370 |
|
|
ingmarstein@3037
|
371 |
- (float) requiredDistanceFromExistingDisplays {
|
|
ingmarstein@3037
|
372 |
return paddingY;
|
|
ingmarstein@3037
|
373 |
}
|
|
ingmarstein@3037
|
374 |
|
|
eridius@2773
|
375 |
@end
|
|
evands@4035
|
376 |
|
|
evands@4035
|
377 |
@implementation NSData (Base64Additions)
|
|
evands@4035
|
378 |
|
|
evands@4035
|
379 |
static char encodingTable[64] = {
|
|
evands@4035
|
380 |
'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
|
|
evands@4035
|
381 |
'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
|
|
evands@4035
|
382 |
'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
|
|
evands@4035
|
383 |
'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/' };
|
|
evands@4035
|
384 |
|
|
evands@4035
|
385 |
- (NSString *) base64EncodingWithLineLength:(unsigned int) lineLength {
|
|
evands@4035
|
386 |
const unsigned char *bytes = [self bytes];
|
|
evands@4035
|
387 |
NSMutableString *result = [NSMutableString stringWithCapacity:[self length]];
|
|
evands@4035
|
388 |
unsigned long ixtext = 0;
|
|
evands@4035
|
389 |
unsigned long lentext = [self length];
|
|
evands@4035
|
390 |
long ctremaining = 0;
|
|
evands@4035
|
391 |
unsigned char inbuf[3], outbuf[4];
|
|
evands@4035
|
392 |
unsigned short i = 0;
|
|
evands@4035
|
393 |
unsigned short charsonline = 0, ctcopy = 0;
|
|
evands@4035
|
394 |
unsigned long ix = 0;
|
|
evands@4035
|
395 |
|
|
evands@4035
|
396 |
while( YES ) {
|
|
evands@4035
|
397 |
ctremaining = lentext - ixtext;
|
|
evands@4035
|
398 |
if( ctremaining <= 0 ) break;
|
|
evands@4035
|
399 |
|
|
evands@4035
|
400 |
for( i = 0; i < 3; i++ ) {
|
|
evands@4035
|
401 |
ix = ixtext + i;
|
|
evands@4035
|
402 |
if( ix < lentext ) inbuf[i] = bytes[ix];
|
|
evands@4035
|
403 |
else inbuf [i] = 0;
|
|
evands@4035
|
404 |
}
|
|
evands@4035
|
405 |
|
|
evands@4035
|
406 |
outbuf [0] = (inbuf [0] & 0xFC) >> 2;
|
|
evands@4035
|
407 |
outbuf [1] = ((inbuf [0] & 0x03) << 4) | ((inbuf [1] & 0xF0) >> 4);
|
|
evands@4035
|
408 |
outbuf [2] = ((inbuf [1] & 0x0F) << 2) | ((inbuf [2] & 0xC0) >> 6);
|
|
evands@4035
|
409 |
outbuf [3] = inbuf [2] & 0x3F;
|
|
evands@4035
|
410 |
ctcopy = 4;
|
|
evands@4035
|
411 |
|
|
evands@4035
|
412 |
switch( ctremaining ) {
|
|
evands@4035
|
413 |
case 1:
|
|
evands@4035
|
414 |
ctcopy = 2;
|
|
evands@4035
|
415 |
break;
|
|
evands@4035
|
416 |
case 2:
|
|
evands@4035
|
417 |
ctcopy = 3;
|
|
evands@4035
|
418 |
break;
|
|
evands@4035
|
419 |
}
|
|
evands@4035
|
420 |
|
|
evands@4035
|
421 |
for( i = 0; i < ctcopy; i++ )
|
|
evands@4035
|
422 |
[result appendFormat:@"%c", encodingTable[outbuf[i]]];
|
|
evands@4035
|
423 |
|
|
evands@4035
|
424 |
for( i = ctcopy; i < 4; i++ )
|
|
evands@4035
|
425 |
[result appendString:@"="];
|
|
evands@4035
|
426 |
|
|
evands@4035
|
427 |
ixtext += 3;
|
|
evands@4035
|
428 |
charsonline += 4;
|
|
evands@4035
|
429 |
|
|
evands@4035
|
430 |
if( lineLength > 0 ) {
|
|
evands@4035
|
431 |
if( charsonline >= lineLength ) {
|
|
evands@4035
|
432 |
charsonline = 0;
|
|
evands@4035
|
433 |
[result appendString:@"\n"];
|
|
evands@4035
|
434 |
}
|
|
evands@4035
|
435 |
}
|
|
evands@4035
|
436 |
}
|
|
evands@4035
|
437 |
|
|
evands@4035
|
438 |
return result;
|
|
evands@4035
|
439 |
}
|
|
evands@4035
|
440 |
|
|
evands@4035
|
441 |
- (NSString *) base64Encoding {
|
|
evands@4035
|
442 |
return [self base64EncodingWithLineLength:0];
|
|
evands@4035
|
443 |
}
|
|
evands@4035
|
444 |
|
|
evands@4035
|
445 |
@end
|
|
evands@4035
|
446 |
|
|
evands@4035
|
447 |
@implementation NSImage (PNGRepAddition)
|
|
boredzo@4063
|
448 |
- (NSBitmapImageRep *)GrowlBitmapImageRepForPNG
|
|
boredzo@4037
|
449 |
{
|
|
evands@4048
|
450 |
//Find the biggest image
|
|
boredzo@4037
|
451 |
NSEnumerator *repsEnum = [[self representations] objectEnumerator];
|
|
evands@4048
|
452 |
NSBitmapImageRep *bestRep = nil;
|
|
boredzo@4037
|
453 |
NSImageRep *rep;
|
|
boredzo@4037
|
454 |
Class NSBitmapImageRepClass = [NSBitmapImageRep class];
|
|
evands@4048
|
455 |
float maxWidth = 0;
|
|
boredzo@4037
|
456 |
while ((rep = [repsEnum nextObject])) {
|
|
boredzo@4037
|
457 |
if ([rep isKindOfClass:NSBitmapImageRepClass]) {
|
|
boredzo@4063
|
458 |
//We can't convert a 1-bit image to PNG format (libpng throws an error), so ignore any 1-bit image reps, regardless of size.
|
|
boredzo@4063
|
459 |
if ([rep bitsPerSample] > 1) {
|
|
boredzo@4063
|
460 |
float width = [rep size].width;
|
|
boredzo@4063
|
461 |
if (width >= maxWidth) {
|
|
boredzo@4063
|
462 |
//Cast explanation: GCC warns about us returning an NSImageRep here, presumably because it could be some other kind of NSImageRep if we don't check the class. Fortunately, we have such a check. This cast silences the warning.
|
|
boredzo@4063
|
463 |
bestRep = (NSBitmapImageRep *)rep;
|
|
boredzo@4063
|
464 |
|
|
boredzo@4063
|
465 |
maxWidth = width;
|
|
boredzo@4063
|
466 |
}
|
|
evands@4048
|
467 |
}
|
|
boredzo@4037
|
468 |
}
|
|
boredzo@4037
|
469 |
}
|
|
evands@4048
|
470 |
|
|
evands@4048
|
471 |
return bestRep;
|
|
boredzo@4037
|
472 |
}
|
|
boredzo@4037
|
473 |
|
|
evands@4035
|
474 |
- (NSData *)PNGRepresentation
|
|
evands@4035
|
475 |
{
|
|
boredzo@4063
|
476 |
/* PNG is easy; it supports almost everything TIFF does (not 1-bit images), and NSImage's PNG support is great. */
|
|
boredzo@4063
|
477 |
return ([(NSBitmapImageRep *)[self GrowlBitmapImageRepForPNG] representationUsingType:NSPNGFileType properties:nil]);
|
|
boredzo@4063
|
478 |
}
|
|
boredzo@4063
|
479 |
|
|
boredzo@4063
|
480 |
@end
|