]> git.saurik.com Git - winterboard.git/blame - Settings.mm
Fixed CalendarIcon text styling when isWildcat.
[winterboard.git] / Settings.mm
CommitLineData
224d48e3 1/* WinterBoard - Theme Manager for the iPhone
43fd3a0b 2 * Copyright (C) 2009-2010 Jay Freeman (saurik)
224d48e3
JF
3*/
4
5/*
6 * Redistribution and use in source and binary
7 * forms, with or without modification, are permitted
8 * provided that the following conditions are met:
9 *
10 * 1. Redistributions of source code must retain the
11 * above copyright notice, this list of conditions
12 * and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the
14 * above copyright notice, this list of conditions
15 * and the following disclaimer in the documentation
16 * and/or other materials provided with the
17 * distribution.
18 * 3. The name of the author may not be used to endorse
19 * or promote products derived from this software
20 * without specific prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS''
23 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
24 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
25 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
27 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
28 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
29 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
30 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
32 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
33 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
34 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
35 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36*/
37
38#import <Foundation/Foundation.h>
39#import <UIKit/UIKit.h>
40#import <Preferences/PSListController.h>
41#import <Preferences/PSSpecifier.h>
42#import <Preferences/PSTableCell.h>
43#import <UIKit/UINavigationButton.h>
44
7c88a3f1
JF
45#include <dlfcn.h>
46
47static BOOL (*IsIconHiddenDisplayId)(NSString *);
48static BOOL (*HideIconViaDisplayId)(NSString *);
49static BOOL (*UnHideIconViaDisplayId)(NSString *);
50
51static const NSString *WinterBoardDisplayID = @"com.saurik.WinterBoard";
52
224d48e3
JF
53extern NSString *PSTableCellKey;
54extern "C" UIImage *_UIImageWithName(NSString *);
55
56static UIImage *checkImage;
57static UIImage *uncheckedImage;
58
59static BOOL settingsChanged;
60static NSMutableDictionary *_settings;
61static NSString *_plist;
62
265e19b2
JF
63/* [NSObject yieldToSelector:(withObject:)] {{{*/
64@interface NSObject (wb$yieldToSelector)
65- (id) wb$yieldToSelector:(SEL)selector withObject:(id)object;
66- (id) wb$yieldToSelector:(SEL)selector;
67@end
68
69@implementation NSObject (Cydia)
70
71- (void) wb$doNothing {
72}
73
74- (void) wb$_yieldToContext:(NSMutableArray *)context {
75 NSAutoreleasePool *pool([[NSAutoreleasePool alloc] init]);
76
77 SEL selector(reinterpret_cast<SEL>([[context objectAtIndex:0] pointerValue]));
78 id object([[context objectAtIndex:1] nonretainedObjectValue]);
79 volatile bool &stopped(*reinterpret_cast<bool *>([[context objectAtIndex:2] pointerValue]));
80
81 /* XXX: deal with exceptions */
82 id value([self performSelector:selector withObject:object]);
83
84 NSMethodSignature *signature([self methodSignatureForSelector:selector]);
85 [context removeAllObjects];
86 if ([signature methodReturnLength] != 0 && value != nil)
87 [context addObject:value];
88
89 stopped = true;
90
91 [self
92 performSelectorOnMainThread:@selector(wb$doNothing)
93 withObject:nil
94 waitUntilDone:NO
95 ];
96
97 [pool release];
98}
99
100- (id) wb$yieldToSelector:(SEL)selector withObject:(id)object {
101 /*return [self performSelector:selector withObject:object];*/
102
103 volatile bool stopped(false);
104
105 NSMutableArray *context([NSMutableArray arrayWithObjects:
106 [NSValue valueWithPointer:selector],
107 [NSValue valueWithNonretainedObject:object],
108 [NSValue valueWithPointer:const_cast<bool *>(&stopped)],
109 nil]);
110
111 NSThread *thread([[[NSThread alloc]
112 initWithTarget:self
113 selector:@selector(wb$_yieldToContext:)
114 object:context
115 ] autorelease]);
116
117 [thread start];
118
119 NSRunLoop *loop([NSRunLoop currentRunLoop]);
120 NSDate *future([NSDate distantFuture]);
121
122 while (!stopped && [loop runMode:NSDefaultRunLoopMode beforeDate:future]);
123
124 return [context count] == 0 ? nil : [context objectAtIndex:0];
125}
126
127- (id) wb$yieldToSelector:(SEL)selector {
128 return [self wb$yieldToSelector:selector withObject:nil];
129}
130
131@end
132/* }}} */
133
224d48e3
JF
134/* Theme Settings Controller {{{ */
135@interface WBSThemesController: PSViewController <UITableViewDelegate, UITableViewDataSource> {
136 UITableView *_tableView;
137 NSMutableArray *_themes;
138}
139
140@property (nonatomic, retain) NSMutableArray *themes;
141
142+ (void) load;
143
144- (id) initForContentSize:(CGSize)size;
145- (id) view;
146- (id) navigationTitle;
147- (void) themesChanged;
148
149- (int) numberOfSectionsInTableView:(UITableView *)tableView;
150- (id) tableView:(UITableView *)tableView titleForHeaderInSection:(int)section;
151- (int) tableView:(UITableView *)tableView numberOfRowsInSection:(int)section;
152- (id) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
153- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
154- (void) tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath;
155- (UITableViewCellEditingStyle) tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath;
156- (BOOL) tableView:(UITableView *)tableView shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath;
157- (BOOL) tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath;
7c88a3f1 158
224d48e3
JF
159@end
160
161@implementation WBSThemesController
162
163@synthesize themes = _themes;
164
165+ (void) load {
166 NSAutoreleasePool *pool([[NSAutoreleasePool alloc] init]);
167 checkImage = [_UIImageWithName(@"UIPreferencesBlueCheck.png") retain];
168 uncheckedImage = [[UIImage imageWithContentsOfFile:@"/System/Library/PreferenceBundles/WinterBoardSettings.bundle/SearchResultsCheckmarkClear.png"] retain];
169 [pool release];
170}
171
172- (id) initForContentSize:(CGSize)size {
173 if ((self = [super initForContentSize:size]) != nil) {
174 self.themes = [_settings objectForKey:@"Themes"];
175 if (!_themes) {
176 if (NSString *theme = [_settings objectForKey:@"Theme"]) {
177 self.themes = [NSMutableArray arrayWithObject:
178 [NSMutableDictionary dictionaryWithObjectsAndKeys:
179 theme, @"Name",
180 [NSNumber numberWithBool:YES], @"Active", nil]];
181 [_settings removeObjectForKey:@"Theme"];
182 }
183 if (!_themes)
184 self.themes = [NSMutableArray array];
185 [_settings setObject:_themes forKey:@"Themes"];
186 }
187
188 NSMutableArray *themesOnDisk([NSMutableArray array]);
189
190 [themesOnDisk
191 addObjectsFromArray:[[NSFileManager defaultManager]
192 contentsOfDirectoryAtPath:@"/Library/Themes" error:NULL]
193 ];
194
195 [themesOnDisk addObjectsFromArray:[[NSFileManager defaultManager]
196 contentsOfDirectoryAtPath:[NSString stringWithFormat:@"%@/Library/SummerBoard/Themes", NSHomeDirectory()]
197 error:NULL
198 ]];
199
200 for (int i = 0, count = [themesOnDisk count]; i < count; i++) {
201 NSString *theme = [themesOnDisk objectAtIndex:i];
202 if ([theme hasSuffix:@".theme"])
203 [themesOnDisk replaceObjectAtIndex:i withObject:[theme stringByDeletingPathExtension]];
204 }
205
206 NSMutableSet *themesSet([NSMutableSet set]);
207
208 for (int i = 0, count = [_themes count]; i < count; i++) {
209 NSDictionary *theme([_themes objectAtIndex:i]);
210 NSString *name([theme objectForKey:@"Name"]);
211
212 if (!name || ![themesOnDisk containsObject:name]) {
213 [_themes removeObjectAtIndex:i];
214 i--;
215 count--;
216 } else {
217 [themesSet addObject:name];
218 }
219 }
220
221 for (NSString *theme in themesOnDisk) {
222 if ([themesSet containsObject:theme])
223 continue;
224 [themesSet addObject:theme];
225
226 [_themes insertObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
227 theme, @"Name",
228 [NSNumber numberWithBool:NO], @"Active",
229 nil] atIndex:0];
230 }
231
232 _tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, 320, 480-64) style:UITableViewStyleGrouped];
233 [_tableView setDataSource:self];
234 [_tableView setDelegate:self];
235 [_tableView setEditing:YES];
236 [_tableView setAllowsSelectionDuringEditing:YES];
237 [self showLeftButton:@"WinterBoard" withStyle:1 rightButton:nil withStyle:0];
238 }
239 return self;
240}
241
242- (void) dealloc {
243 [_tableView release];
244 [_themes release];
245 [super dealloc];
246}
247
248- (id) navigationTitle {
249 return @"Themes";
250}
251
252- (id) view {
253 return _tableView;
254}
255
256- (void) themesChanged {
257 settingsChanged = YES;
258}
259
260/* UITableViewDelegate / UITableViewDataSource Methods {{{ */
261- (int) numberOfSectionsInTableView:(UITableView *)tableView {
262 return 1;
263}
264
265- (id) tableView:(UITableView *)tableView titleForHeaderInSection:(int)section {
266 return nil;
267}
268
269- (int) tableView:(UITableView *)tableView numberOfRowsInSection:(int)section {
270 return _themes.count;
271}
272
273- (id) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
274 UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ThemeCell"];
275 if (!cell) {
276 cell = [[[UITableViewCell alloc] initWithFrame:CGRectMake(0, 0, 100, 100) reuseIdentifier:@"ThemeCell"] autorelease];
277 //[cell setTableViewStyle:UITableViewCellStyleDefault];
278 }
279
280 NSDictionary *theme([_themes objectAtIndex:indexPath.row]);
281 cell.text = [theme objectForKey:@"Name"];
282 cell.hidesAccessoryWhenEditing = NO;
283 NSNumber *active([theme objectForKey:@"Active"]);
284 BOOL inactive(active == nil || ![active boolValue]);
285 [cell setImage:(inactive ? uncheckedImage : checkImage)];
286 return cell;
287}
288
289- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
290 UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
291 NSMutableDictionary *theme = [_themes objectAtIndex:indexPath.row];
292 NSNumber *active = [theme objectForKey:@"Active"];
293 BOOL inactive = active == nil || ![active boolValue];
294 [theme setObject:[NSNumber numberWithBool:inactive] forKey:@"Active"];
295 [cell setImage:(!inactive ? uncheckedImage : checkImage)];
296 [tableView deselectRowAtIndexPath:(NSIndexPath *)indexPath animated:YES];
297 [self themesChanged];
298}
299
300- (void) tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {
301 NSUInteger fromIndex = [fromIndexPath row];
302 NSUInteger toIndex = [toIndexPath row];
303 if (fromIndex == toIndex)
304 return;
305 NSMutableDictionary *theme = [[[_themes objectAtIndex:fromIndex] retain] autorelease];
306 [_themes removeObjectAtIndex:fromIndex];
307 [_themes insertObject:theme atIndex:toIndex];
308 [self themesChanged];
309}
310
311- (UITableViewCellEditingStyle) tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
312 return UITableViewCellEditingStyleNone;
313}
314
315- (BOOL) tableView:(UITableView *)tableView shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath {
316 return NO;
317}
318
319- (BOOL) tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
320 return YES;
321}
322/* }}} */
323@end
324/* }}} */
325
326@interface WBSettingsController: PSListController {
327}
328
329- (id) initForContentSize:(CGSize)size;
330- (void) dealloc;
331- (void) suspend;
332- (void) navigationBarButtonClicked:(int)buttonIndex;
333- (void) viewWillRedisplay;
334- (void) pushController:(id)controller;
335- (id) specifiers;
336- (void) settingsChanged;
337- (NSString *) title;
338- (void) setPreferenceValue:(id)value specifier:(PSSpecifier *)spec;
339- (id) readPreferenceValue:(PSSpecifier *)spec;
340
341@end
342
343@implementation WBSettingsController
344
7c88a3f1 345+ (void) load {
7c88a3f1 346 void *libhide(dlopen("/usr/lib/hide.dylib", RTLD_LAZY));
7c88a3f1 347 IsIconHiddenDisplayId = reinterpret_cast<BOOL (*)(NSString *)>(dlsym(libhide, "IsIconHiddenDisplayId"));
7c88a3f1 348 HideIconViaDisplayId = reinterpret_cast<BOOL (*)(NSString *)>(dlsym(libhide, "HideIconViaDisplayId"));
7c88a3f1 349 UnHideIconViaDisplayId = reinterpret_cast<BOOL (*)(NSString *)>(dlsym(libhide, "UnHideIconViaDisplayId"));
7c88a3f1
JF
350}
351
224d48e3
JF
352- (id) initForContentSize:(CGSize)size {
353 if ((self = [super initForContentSize:size]) != nil) {
354 _plist = [[NSString stringWithFormat:@"%@/Library/Preferences/com.saurik.WinterBoard.plist", NSHomeDirectory()] retain];
355 _settings = [([NSMutableDictionary dictionaryWithContentsOfFile:_plist] ?: [NSMutableDictionary dictionary]) retain];
7c88a3f1
JF
356
357 [_settings setObject:[NSNumber numberWithBool:IsIconHiddenDisplayId(WinterBoardDisplayID)] forKey:@"IconHidden"];
224d48e3
JF
358 } return self;
359}
360
361- (void) dealloc {
362 [_settings release];
363 [_plist release];
364 [super dealloc];
365}
366
43fd3a0b 367- (void) __optimizeThemes {
265e19b2
JF
368 system("/usr/libexec/winterboard/Optimize");
369}
370
371- (void) optimizeThemes {
43fd3a0b
JF
372 UIActionSheet *sheet([[[UIActionSheet alloc]
373 initWithTitle:@"Optimize Themes"
374 buttons:[NSArray arrayWithObjects:@"Optimize", @"Cancel", nil]
375 defaultButtonIndex:1
376 delegate:self
377 context:@"optimize"
378 ] autorelease]);
379
380 [sheet setBodyText:@"Please note that this setting /replaces/ the PNG files that came with the theme. PNG files that have been iPhone-optimized cannot be viewed on a normal computer unless they are first deoptimized. You can use Cydia to reinstall themes that have been optimized in order to revert to the original PNG files."];
381 [sheet setNumberOfRows:1];
382 [sheet setDestructiveButtonIndex:0];
383 [sheet popupAlertAnimated:YES];
384}
385
386- (void) _optimizeThemes {
265e19b2
JF
387 UIView *view([self view]);
388 UIWindow *window([view window]);
389
390 UIProgressHUD *hud([[[UIProgressHUD alloc] initWithWindow:window] autorelease]);
391 [hud setText:@"Reticulating Splines\nPlease Wait (Minutes)"];
392
393 [window setUserInteractionEnabled:NO];
394
395 [window addSubview:hud];
396 [hud show:YES];
43fd3a0b 397 [self wb$yieldToSelector:@selector(__optimizeThemes)];
265e19b2
JF
398 [hud removeFromSuperview];
399
400 [window setUserInteractionEnabled:YES];
401
402 [self settingsChanged];
403}
404
43fd3a0b
JF
405- (void) alertSheet:(UIActionSheet *)sheet buttonClicked:(int)button {
406 NSString *context([sheet context]);
407
408 if ([context isEqualToString:@"optimize"]) {
409 switch (button) {
410 case 1:
411 [self performSelector:@selector(_optimizeThemes) withObject:nil afterDelay:0];
412 break;
413 }
414
415 [sheet dismiss];
416 } else
417 [super alertSheet:sheet buttonClicked:button];
418
419}
420
224d48e3
JF
421- (void) suspend {
422 if (!settingsChanged)
423 return;
424
425 NSData *data([NSPropertyListSerialization dataFromPropertyList:_settings format:NSPropertyListBinaryFormat_v1_0 errorDescription:NULL]);
426 if (!data)
427 return;
428 if (![data writeToFile:_plist options:NSAtomicWrite error:NULL])
429 return;
430
7c88a3f1
JF
431 ([[_settings objectForKey:@"IconHidden"] boolValue] ? HideIconViaDisplayId : UnHideIconViaDisplayId)(WinterBoardDisplayID);
432
224d48e3
JF
433 unlink("/User/Library/Caches/com.apple.springboard-imagecache-icons");
434 unlink("/User/Library/Caches/com.apple.springboard-imagecache-icons.plist");
435 unlink("/User/Library/Caches/com.apple.springboard-imagecache-smallicons");
436 unlink("/User/Library/Caches/com.apple.springboard-imagecache-smallicons.plist");
7c88a3f1
JF
437
438 system("rm -rf /User/Library/Caches/SpringBoardIconCache");
439 system("rm -rf /User/Library/Caches/SpringBoardIconCache-small");
440
224d48e3
JF
441 system("killall SpringBoard");
442}
443
444- (void) navigationBarButtonClicked:(int)buttonIndex {
445 if (!settingsChanged) {
446 [super navigationBarButtonClicked:buttonIndex];
447 return;
448 }
449
450 if (buttonIndex == 0)
451 settingsChanged = NO;
452
453 [self suspend];
454 [self.rootController popController];
455}
456
457- (void) viewWillRedisplay {
458 if (settingsChanged)
459 [self settingsChanged];
460 [super viewWillRedisplay];
461}
462
463- (void) pushController:(id)controller {
464 [self hideNavigationBarButtons];
465 [super pushController:controller];
466}
467
468- (id) specifiers {
469 if (!_specifiers)
470 _specifiers = [[self loadSpecifiersFromPlistName:@"WinterBoard" target:self] retain];
471 return _specifiers;
472}
473
474- (void) settingsChanged {
475 [self showLeftButton:@"Respring" withStyle:2 rightButton:@"Cancel" withStyle:0];
476 settingsChanged = YES;
477}
478
479- (NSString *) title {
480 return @"WinterBoard";
481}
482
483- (void) setPreferenceValue:(id)value specifier:(PSSpecifier *)spec {
7c88a3f1 484 NSString *key([spec propertyForKey:@"key"]);
224d48e3
JF
485 if ([[spec propertyForKey:@"negate"] boolValue])
486 value = [NSNumber numberWithBool:(![value boolValue])];
7c88a3f1 487 [_settings setValue:value forKey:key];
224d48e3
JF
488 [self settingsChanged];
489}
490
491- (id) readPreferenceValue:(PSSpecifier *)spec {
492 NSString *key([spec propertyForKey:@"key"]);
493 id defaultValue([spec propertyForKey:@"default"]);
494 id plistValue([_settings objectForKey:key]);
495 if (!plistValue)
496 return defaultValue;
497 if ([[spec propertyForKey:@"negate"] boolValue])
498 plistValue = [NSNumber numberWithBool:(![plistValue boolValue])];
499 return plistValue;
500}
501
502@end