Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 508 lines (414 sloc) 16.547 kb
ea62074 @nikitar initial commit
authored
1 /*
2 * Created by Nikita Rybak on Feb 2 2011.
3 *
4 * All rights reserved.
5 *
6 * Permission is hereby granted, free of charge,
7 * to any person obtaining a copy of this software and associated documentation
8 * files (the "Software"), to deal in the Software without restriction,
9 * including without limitation the rights to use, copy, modify, merge, publish,
10 * distribute, sublicense, and/or sell copies of the Software, and to permit
11 * persons to whom the Software is furnished to do so, subject to the following
12 * conditions:
13 *
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 * SOFTWARE.
24 */
25
26 #import "PreferencePaneController.h"
27
4e49cb2 @nikitar reputation tracker
authored
28
ea62074 @nikitar initial commit
authored
29 @interface PreferencePaneController()
30 - (LSSharedFileListItemRef)findStartupItem:(NSString *)appPath;
f71f43c @nikitar settings moved to a separate class + profile lookup
authored
31 - (void)updateUserInfoWithProfiles:(NSArray *)profiles
32 andGlobalUserId:(NSString *)globalUserId
33 andFlair:(NSData *)flairData;
ea62074 @nikitar initial commit
authored
34 @end
35
36
37
f71f43c @nikitar settings moved to a separate class + profile lookup
authored
38
ea62074 @nikitar initial commit
authored
39 @implementation PreferencePaneController
40
41 - (id)initWithBundle:(NSBundle *)bundle {
f71f43c @nikitar settings moved to a separate class + profile lookup
authored
42 self = [super initWithBundle:bundle];
43 if (self != nil) {
44 queryTool = [[StackExchangeQueryTool alloc] init];
ea62074 @nikitar initial commit
authored
45 }
46
47 return self;
48 }
49
f71f43c @nikitar settings moved to a separate class + profile lookup
authored
50 -(void)dealloc {
51 [queryTool release];
52
53 [super dealloc];
54 }
55
ea62074 @nikitar initial commit
authored
56 - (NSString *)mainNibName {
57 return @"PrefPane";
58 }
59
60 - (void)mainViewDidLoad {
f71f43c @nikitar settings moved to a separate class + profile lookup
authored
61 // NSLog(@"mainViewDidLoad %@", [profileURL delegate]);
62 }
63
64 - (void)setPersistence:(NewtPersistence *) persistence_ {
65 persistence = persistence_;
ea62074 @nikitar initial commit
authored
66 }
67
68 - (void)willSelect {
f71f43c @nikitar settings moved to a separate class + profile lookup
authored
69 // NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
ea62074 @nikitar initial commit
authored
70
71 [launchOnStartup setState:0];
72 [launchOnStartup setTarget:self];
73 [launchOnStartup setAction:@selector(updateStartupLaunchAction:)];
74
75 NSString *appPath = [[NSBundle mainBundle] bundlePath];
76 if ([self findStartupItem:appPath]) {
77 [launchOnStartup setState:1];
78 }
79
80
f71f43c @nikitar settings moved to a separate class + profile lookup
authored
81 NSData *flair = [persistence objectForKey:@"user_flair"];
82 if (flair != nil) {
3e61851 @nikitar enter user profile in a new window
authored
83 // NSLog(@"global id %@", [persistence objectForKey:@"user_global_id"]);
f71f43c @nikitar settings moved to a separate class + profile lookup
authored
84
85 NSImage *image = [[NSImage alloc] initWithData:flair];
86 NSSize newSize;
87 newSize.height = 58;
88 newSize.width = 208;
89 [image setSize:newSize];
90 [profileImage setImage:image];
91 [image release];
ea62074 @nikitar initial commit
authored
92 }
f71f43c @nikitar settings moved to a separate class + profile lookup
authored
93
94 [sitesTable initWithPersistence:persistence];
ea62074 @nikitar initial commit
authored
95
96 siteInEdit = NULL;
97 [tagsField setTokenizingCharacterSet:[NSCharacterSet characterSetWithCharactersInString:@", "]];
98 [self loadConfigurationForSite:NULL];
99 }
100
101 - (void)willUnselect {
102 // save unsaved fields
103 [self saveConfigurationForCurrentSite];
104
105 // save all data to memory
106 [self flushPreferences];
107
108 if (siteInEdit) {
109 [siteInEdit release];
110 }
111 }
112
113
114
115 // loads preferences view from .xib file and puts it in a new window
116 -(void) displayPreferences {
117 if (window != NULL) {
118 [window orderFrontRegardless];
119 [NSApp activateIgnoringOtherApps:YES];
120 return;
121 }
122
123 NSView *prefView;
124 if ([self loadMainView]) {
125 [self willSelect];
126 prefView = [self mainView];
127
128 // create preferences window
129 NSSize prefSize = [prefView frame].size;
130 NSRect frame = NSMakeRect(200, 200, prefSize.width, prefSize.height);
131 window = [[NSWindow alloc] initWithContentRect:frame
132 styleMask:NSTitledWindowMask | NSClosableWindowMask
133 backing:NSBackingStoreBuffered
134 defer:NO];
135 [window center];
136 [window setContentView:prefView];
137 [window setTitle:@"Newt Preferences"];
3e61851 @nikitar enter user profile in a new window
authored
138 [window setDelegate:self];
ea62074 @nikitar initial commit
authored
139 [window makeKeyAndOrderFront:NSApp];
140 [NSApp activateIgnoringOtherApps:YES];
141
142 [self didSelect];
143 } else {
144 NSLog(@"load preferences error");
145 }
146 }
147
148 -(void) closePreferences {
149 [self willUnselect];
150
151 // window will be closed automatically
152
f71f43c @nikitar settings moved to a separate class + profile lookup
authored
153 // [persistence release];
7ae9d06 @nikitar user profile completed + minor improvements
authored
154 [profileInputWindow close];
155 [profileInputWindow release];
156 profileInputWindow = nil;
ea62074 @nikitar initial commit
authored
157
158 // displayPreferences checks for NULL to see whether pref window is currently open
159 window = NULL;
160
161 [self didUnselect];
162 }
163
164 - (IBAction)updateFilterAction:(id)sender {
165 NSString *search = [siteSearchField stringValue];
166 [sitesTable applyFilter:search];
167 }
168
169 - (IBAction)updateStartupLaunchAction:(id)sender {
170 NSString *appPath = [[NSBundle mainBundle] bundlePath];
171 CFURLRef url = (CFURLRef)[NSURL fileURLWithPath:appPath];
172 LSSharedFileListRef loginItems = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
173 if (loginItems) {
174 if ([launchOnStartup state]) {
175 //Insert an item to the list.
176 LSSharedFileListItemRef item = LSSharedFileListInsertItemURL(loginItems, kLSSharedFileListItemLast, NULL, NULL, url, NULL, NULL);
177 if (item) {
178 CFRelease(item);
179 }
180 } else {
181 LSSharedFileListItemRef itemRef = [self findStartupItem:appPath];
182 if (itemRef) {
183 LSSharedFileListItemRemove(loginItems, itemRef);
184 }
185 }
186
187 CFRelease(loginItems);
188 }
189 }
190
191 - (LSSharedFileListItemRef)findStartupItem:(NSString *)appPath {
192 LSSharedFileListRef loginItems = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
193
194 if (loginItems) {
195 UInt32 seedValue;
196 NSArray *loginItemsArray = (NSArray *)LSSharedFileListCopySnapshot(loginItems, &seedValue);
197 CFRelease(loginItems);
198
199 for (int i = 0; i < [loginItemsArray count]; ++i) {
200 LSSharedFileListItemRef itemRef = (LSSharedFileListItemRef)[loginItemsArray objectAtIndex:i];
201
202 //Resolve the item with URL
203 CFURLRef url;
204 if (LSSharedFileListItemResolve(itemRef, 0, (CFURLRef*) &url, NULL) == noErr) {
205 NSString *urlPath = [(NSURL*)url path];
206 CFRelease(url);
207 if ([urlPath compare:appPath] == NSOrderedSame){
208 CFRetain(itemRef);
209 [loginItemsArray release];
210 return itemRef;
211 }
212 }
213 }
214 [loginItemsArray release];
215 }
216
217 return nil;
218 }
219
220 - (void)loadConfigurationForSite:(NSString *)key {
221 if (siteInEdit) {
222 [siteInEdit release];
223 }
224
225 if (key == NULL) {
226 siteInEdit = NULL;
227 [tagsField setEditable:FALSE];
228 [tagsField setEnabled:FALSE];
229 [tagsField setObjectValue:@""];
230 return;
231 }
232
233 siteInEdit = [[key copy] retain];
234
235 // fetch user's favourite tags
f71f43c @nikitar settings moved to a separate class + profile lookup
authored
236 NSDictionary *site = [persistence siteForKey:key];
237 NSArray *tags = [site objectForKey:@"favourite_tags"];
ea62074 @nikitar initial commit
authored
238 if (tags == NULL) {
239 tags = [NSArray array];
240 }
241 [tagsField setObjectValue:tags];
242
f71f43c @nikitar settings moved to a separate class + profile lookup
authored
243 NSNumber *enable = [site objectForKey:@"enabled"];
ea62074 @nikitar initial commit
authored
244 BOOL boolEnable = (enable != NULL) && [enable boolValue];
245 [tagsField setEditable:boolEnable];
246 [tagsField setEnabled:boolEnable];
247 }
248
249 - (void)saveConfigurationForCurrentSite {
250 if (siteInEdit == NULL) {
251 return;
252 }
253
254 NSArray *tags = [tagsField objectValue];
f71f43c @nikitar settings moved to a separate class + profile lookup
authored
255 [persistence setObject:tags forSite:siteInEdit andKey:@"favourite_tags"];
ea62074 @nikitar initial commit
authored
256 }
257
258 - (void)flushPreferences {
f71f43c @nikitar settings moved to a separate class + profile lookup
authored
259 // NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
260 // [defaults setObject:preferences forKey:@"preferences"];
261 // [defaults synchronize];
262 [persistence synchronize];
ea62074 @nikitar initial commit
authored
263 }
264
265 - (void)windowDidResignKey:(NSNotification *)notification {
3e61851 @nikitar enter user profile in a new window
authored
266 if ([notification object] == window) {
267 // will also be invoked when window is closed, so check first
268 if (window != NULL) {
269 [self flushPreferences];
270 }
ea62074 @nikitar initial commit
authored
271 }
272 }
273
274 - (void)windowWillClose:(NSNotification *)notification {
3e61851 @nikitar enter user profile in a new window
authored
275 if ([notification object] == window) {
276 [self closePreferences];
277 } else if ([notification object] == profileInputWindow) {
278 [NSApp stopModal];
279 [window makeKeyAndOrderFront:NSApp];
280 [NSApp activateIgnoringOtherApps:YES];
281 }
ea62074 @nikitar initial commit
authored
282 }
283
3e61851 @nikitar enter user profile in a new window
authored
284 - (BOOL)windowShouldClose:(id)sender {
285 if (sender == profileInputWindow) {
286 return activity != FETCHING_USER_PROFILE;
287 }
288 return TRUE;
289 }
290
291
ea62074 @nikitar initial commit
authored
292 - (BOOL)control:(NSControl *)control textShouldEndEditing:(NSText *)fieldEditor {
293 [self saveConfigurationForCurrentSite];
294 return TRUE;
295 }
296
3e61851 @nikitar enter user profile in a new window
authored
297 - (IBAction)selectUserButton:(id)sender {
298 [profileInputWindow makeKeyAndOrderFront:NSApp];
299 [profileInputWindow center];
7ae9d06 @nikitar user profile completed + minor improvements
authored
300 // [NSApp runModalForWindow:profileInputWindow];
3e61851 @nikitar enter user profile in a new window
authored
301 }
302
303 - (IBAction)confirmUserSelection:(id)sender {
304 // NSLog(@"updateProfileURL %@ !", [profileURL stringValue]);
305
306 activity = FETCHING_USER_PROFILE;
307 // [profileProgressIndicator setUsesThreadedAnimation:TRUE];
308 [profileProgressIndicator startAnimation:self];
309 [searchUserButton setEnabled:FALSE];
310 [profileURL setEditable:FALSE];
311
312 [self updateProfileURL];
313 }
314
315 - (void)profileSearchError:(NSString *)text {
316 activity = 0;
317 [profileProgressIndicator stopAnimation:self];
318 [searchUserButton setEnabled:TRUE];
319 [profileURL setEditable:TRUE];
320
321 NSRect frame = [profileInputWindow frame];
322 frame.size.height = 97;
323 [profileInputWindow setFrame:frame display:TRUE animate:TRUE];
324 [profileSearchError setStringValue:text];
325 [profileSearchError setHidden:FALSE];
326
327 NSPoint errorPosition = [profileSearchError frame].origin;
328 errorPosition.y = 10;
329 [profileSearchError setFrameOrigin:errorPosition];
330 }
331
332 - (IBAction)updateProfileURL {
f71f43c @nikitar settings moved to a separate class + profile lookup
authored
333 NSString *url = [profileURL stringValue];
cf1063f @nikitar dmg generation and final 2.0 release
authored
334 // NSLog(@"updateProfileURL %@ ?", url);
3e61851 @nikitar enter user profile in a new window
authored
335 // [self profileSearchError:@"some text"];
f71f43c @nikitar settings moved to a separate class + profile lookup
authored
336
337 // no regular expressions in this fucking language...
338
339 if (![url hasPrefix:@"http://"]) {
340 url = [@"http://" stringByAppendingString:url];
341 }
342
343 NSDictionary *site = nil;
344 for (NSString *key in [persistence sites]) {
345 if ([url hasPrefix:key]) {
346 site = [persistence siteForKey:key];
347 break;
348 }
349 }
350 if (site == nil) {
3e61851 @nikitar enter user profile in a new window
authored
351 [self profileSearchError:@"Profile URL is expected, e.g. stackoverflow.com/users/22656"];
f71f43c @nikitar settings moved to a separate class + profile lookup
authored
352 return;
353 }
354
355 // remove 'http://stackoverflow.com/users/' prefix
356 NSString *suffix = [url substringFromIndex:[[site objectForKey:@"site_url"] length] + 7];
357 NSString *id = [[suffix componentsSeparatedByString:@"/"] objectAtIndex:0];
358
359 QueryToolSuccessHandler userDataHandler = ^(NSDictionary *result) {
360 NSArray *users = [result objectForKey:@"users"];
361 if ([users count] == 0) {
328788e @nikitar comment/answer notifications
authored
362 // no such user
3e61851 @nikitar enter user profile in a new window
authored
363 [self profileSearchError:@"User with such id doesn't exist."];
f71f43c @nikitar settings moved to a separate class + profile lookup
authored
364 }
365
366 // save association id
367 NSDictionary *userData = [users objectAtIndex:0];
368 NSString *associationId = [userData objectForKey:@"association_id"];
369
370
371 // fetch information about user's profiles across Stack Exchange network
372 QueryToolSuccessHandler globalUserDataHandler = ^(NSDictionary *result) {
373 NSArray *profiles = [result objectForKey:@"associated_users"];
374
375 // retrieve flair image for the user
376 NSString *flairURL = [NSString stringWithFormat:@"http://stackexchange.com/users/flair/%@.png", associationId];
377 NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString: flairURL]];
378 URLConnectionDelegate *flairHandler = [[[URLConnectionDelegate alloc] initWithSuccessHandler:^(NSData *flair) {
379
380 [self updateUserInfoWithProfiles:profiles
381 andGlobalUserId:associationId
382 andFlair:flair];
383
384 }] autorelease];
385
386 // will be released from delegate
387 NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request
388 delegate:flairHandler];
389 if (!connection) {
390 NSLog(@"Couldn't open connection for url %@", flairURL);
391 }
392 };
393 [queryTool execute:@"http://stackauth.com"
394 withMethod:[NSString stringWithFormat:@"users/%@/associated", associationId]
395 andParameters:[NSDictionary dictionary]
396 onSuccess:globalUserDataHandler];
397
398 };
399
400 NSString *apiEndpoint = [site objectForKey:@"api_endpoint"];
401 [queryTool execute:apiEndpoint
402 withMethod:[@"users/" stringByAppendingString:id]
403 andParameters:[NSDictionary dictionary]
404 onSuccess:userDataHandler];
405 }
406
407 - (void)updateUserInfoWithProfiles:(NSArray *)profiles
408 andGlobalUserId:(NSString *)globalUserId
409 andFlair:(NSData *)flairData {
cf1063f @nikitar dmg generation and final 2.0 release
authored
410 // NSLog(@"updateUserInfoWithProfiles");
3e61851 @nikitar enter user profile in a new window
authored
411 activity = 0;
412 [profileProgressIndicator stopAnimation:self];
413 [searchUserButton setEnabled:TRUE];
414 [profileURL setEditable:TRUE];
415 [profileInputWindow close];
416
417
4e49cb2 @nikitar reputation tracker
authored
418 // profiles = [profiles sortedArrayUsingFunction:sortSitesByUsage context:nil];
419 //
420 // NSMutableArray *sortedProfiles = [NSMutableArray arrayWithCapacity:[profiles count]];
421 //
422 // // clear any data from previous user account, assuming there was one
423 // for (NSString *siteKey in [persistence sites]) {
424 // NSMutableDictionary *site = [persistence siteForKey:siteKey];
425 // [site removeObjectForKey:@"user_reputation"];
426 // [site removeObjectForKey:@"user_email_hash"];
427 // [site removeObjectForKey:@"user_name"];
428 // [site removeObjectForKey:@"user_type"];
429 // [site removeObjectForKey:@"user_id"];
430 // }
431 //
432 // // persist profile data
433 // for (NSDictionary *profile in profiles) {
434 // NSString *siteKey = [[profile objectForKey:@"on_site"] objectForKey:@"site_url"];
435 // NSMutableDictionary *site = [persistence siteForKey:siteKey];
436 //
437 // NSString *userType = [profile objectForKey:@"user_type"];
438 // if ([userType isEqualToString:@"registered"] || [userType isEqualToString:@"moderator"]) {
439 // [site setObject:[profile objectForKey:@"user_id"] forKey:@"user_id"];
440 // [site setObject:userType forKey:@"user_type"];
441 // [site setObject:[profile objectForKey:@"display_name"] forKey:@"user_name"];
442 // [site setObject:[profile objectForKey:@"reputation"] forKey:@"user_reputation"];
443 // [site setObject:[profile objectForKey:@"email_hash"] forKey:@"user_email_hash"];
444 // }
445 //
446 // [sortedProfiles addObject:siteKey];
447 // }
448 [self updateProfiles:profiles];
449
450 [persistence setObject:flairData forKey:@"user_flair"];
451 [persistence setObject:globalUserId forKey:@"user_global_id"];
452 // [persistence setObject:sortedProfiles forKey:@"most_used_sites"];
453 [persistence synchronize];
454
455 // present flair image, so user knows it's _his_ account
456 NSImage *image = [[NSImage alloc] initWithData:flairData];
457 NSSize newSize;
458 newSize.height = 58;
459 newSize.width = 208;
460 [image setSize:newSize];
461 [profileImage setImage:image];
462 [image release];
463
464 // reorder table according to new user accouns data
465 [self updateFilterAction:self];
466 }
467
468 - (void)updateProfiles:(NSArray *)profiles {
f71f43c @nikitar settings moved to a separate class + profile lookup
authored
469 profiles = [profiles sortedArrayUsingFunction:sortSitesByUsage context:nil];
470
471 NSMutableArray *sortedProfiles = [NSMutableArray arrayWithCapacity:[profiles count]];
4e49cb2 @nikitar reputation tracker
authored
472
7ae9d06 @nikitar user profile completed + minor improvements
authored
473 // clear any data from previous user account, assuming there was one
474 for (NSString *siteKey in [persistence sites]) {
475 NSMutableDictionary *site = [persistence siteForKey:siteKey];
476 [site removeObjectForKey:@"user_reputation"];
477 [site removeObjectForKey:@"user_email_hash"];
478 [site removeObjectForKey:@"user_name"];
479 [site removeObjectForKey:@"user_type"];
480 [site removeObjectForKey:@"user_id"];
481 }
f71f43c @nikitar settings moved to a separate class + profile lookup
authored
482
7ae9d06 @nikitar user profile completed + minor improvements
authored
483 // persist profile data
f71f43c @nikitar settings moved to a separate class + profile lookup
authored
484 for (NSDictionary *profile in profiles) {
485 NSString *siteKey = [[profile objectForKey:@"on_site"] objectForKey:@"site_url"];
486 NSMutableDictionary *site = [persistence siteForKey:siteKey];
4e49cb2 @nikitar reputation tracker
authored
487 if (site == nil) {
488 // probably new site was added, but daily site data update wasn't run yet.
489 continue;
490 }
f71f43c @nikitar settings moved to a separate class + profile lookup
authored
491
492 NSString *userType = [profile objectForKey:@"user_type"];
493 if ([userType isEqualToString:@"registered"] || [userType isEqualToString:@"moderator"]) {
494 [site setObject:[profile objectForKey:@"user_id"] forKey:@"user_id"];
495 [site setObject:userType forKey:@"user_type"];
496 [site setObject:[profile objectForKey:@"display_name"] forKey:@"user_name"];
497 [site setObject:[profile objectForKey:@"reputation"] forKey:@"user_reputation"];
498 [site setObject:[profile objectForKey:@"email_hash"] forKey:@"user_email_hash"];
499 }
500
501 [sortedProfiles addObject:siteKey];
502 }
503
504 [persistence setObject:sortedProfiles forKey:@"most_used_sites"];
505 }
506
ea62074 @nikitar initial commit
authored
507 @end
Something went wrong with that request. Please try again.