]> git.saurik.com Git - winterboard.git/blob - Settings.mm
Added a forward for WBBadgeLabel description.
[winterboard.git] / Settings.mm
1 /* WinterBoard - Theme Manager for the iPhone
2 * Copyright (C) 2009 Jay Freeman (saurik)
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
45 extern NSString *PSTableCellKey;
46 extern "C" UIImage *_UIImageWithName(NSString *);
47
48 static UIImage *checkImage;
49 static UIImage *uncheckedImage;
50
51 static BOOL settingsChanged;
52 static NSMutableDictionary *_settings;
53 static NSString *_plist;
54
55 /* Theme Settings Controller {{{ */
56 @interface WBSThemesController: PSViewController <UITableViewDelegate, UITableViewDataSource> {
57 UITableView *_tableView;
58 NSMutableArray *_themes;
59 }
60
61 @property (nonatomic, retain) NSMutableArray *themes;
62
63 + (void) load;
64
65 - (id) initForContentSize:(CGSize)size;
66 - (id) view;
67 - (id) navigationTitle;
68 - (void) themesChanged;
69
70 - (int) numberOfSectionsInTableView:(UITableView *)tableView;
71 - (id) tableView:(UITableView *)tableView titleForHeaderInSection:(int)section;
72 - (int) tableView:(UITableView *)tableView numberOfRowsInSection:(int)section;
73 - (id) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
74 - (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
75 - (void) tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath;
76 - (UITableViewCellEditingStyle) tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath;
77 - (BOOL) tableView:(UITableView *)tableView shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath;
78 - (BOOL) tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath;
79 @end
80
81 @implementation WBSThemesController
82
83 @synthesize themes = _themes;
84
85 + (void) load {
86 NSAutoreleasePool *pool([[NSAutoreleasePool alloc] init]);
87 checkImage = [_UIImageWithName(@"UIPreferencesBlueCheck.png") retain];
88 uncheckedImage = [[UIImage imageWithContentsOfFile:@"/System/Library/PreferenceBundles/WinterBoardSettings.bundle/SearchResultsCheckmarkClear.png"] retain];
89 [pool release];
90 }
91
92 - (id) initForContentSize:(CGSize)size {
93 if ((self = [super initForContentSize:size]) != nil) {
94 self.themes = [_settings objectForKey:@"Themes"];
95 if (!_themes) {
96 if (NSString *theme = [_settings objectForKey:@"Theme"]) {
97 self.themes = [NSMutableArray arrayWithObject:
98 [NSMutableDictionary dictionaryWithObjectsAndKeys:
99 theme, @"Name",
100 [NSNumber numberWithBool:YES], @"Active", nil]];
101 [_settings removeObjectForKey:@"Theme"];
102 }
103 if (!_themes)
104 self.themes = [NSMutableArray array];
105 [_settings setObject:_themes forKey:@"Themes"];
106 }
107
108 NSMutableArray *themesOnDisk([NSMutableArray array]);
109
110 [themesOnDisk
111 addObjectsFromArray:[[NSFileManager defaultManager]
112 contentsOfDirectoryAtPath:@"/Library/Themes" error:NULL]
113 ];
114
115 [themesOnDisk addObjectsFromArray:[[NSFileManager defaultManager]
116 contentsOfDirectoryAtPath:[NSString stringWithFormat:@"%@/Library/SummerBoard/Themes", NSHomeDirectory()]
117 error:NULL
118 ]];
119
120 for (int i = 0, count = [themesOnDisk count]; i < count; i++) {
121 NSString *theme = [themesOnDisk objectAtIndex:i];
122 if ([theme hasSuffix:@".theme"])
123 [themesOnDisk replaceObjectAtIndex:i withObject:[theme stringByDeletingPathExtension]];
124 }
125
126 NSMutableSet *themesSet([NSMutableSet set]);
127
128 for (int i = 0, count = [_themes count]; i < count; i++) {
129 NSDictionary *theme([_themes objectAtIndex:i]);
130 NSString *name([theme objectForKey:@"Name"]);
131
132 if (!name || ![themesOnDisk containsObject:name]) {
133 [_themes removeObjectAtIndex:i];
134 i--;
135 count--;
136 } else {
137 [themesSet addObject:name];
138 }
139 }
140
141 for (NSString *theme in themesOnDisk) {
142 if ([themesSet containsObject:theme])
143 continue;
144 [themesSet addObject:theme];
145
146 [_themes insertObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
147 theme, @"Name",
148 [NSNumber numberWithBool:NO], @"Active",
149 nil] atIndex:0];
150 }
151
152 _tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, 320, 480-64) style:UITableViewStyleGrouped];
153 [_tableView setDataSource:self];
154 [_tableView setDelegate:self];
155 [_tableView setEditing:YES];
156 [_tableView setAllowsSelectionDuringEditing:YES];
157 [self showLeftButton:@"WinterBoard" withStyle:1 rightButton:nil withStyle:0];
158 }
159 return self;
160 }
161
162 - (void) dealloc {
163 [_tableView release];
164 [_themes release];
165 [super dealloc];
166 }
167
168 - (id) navigationTitle {
169 return @"Themes";
170 }
171
172 - (id) view {
173 return _tableView;
174 }
175
176 - (void) themesChanged {
177 settingsChanged = YES;
178 }
179
180 /* UITableViewDelegate / UITableViewDataSource Methods {{{ */
181 - (int) numberOfSectionsInTableView:(UITableView *)tableView {
182 return 1;
183 }
184
185 - (id) tableView:(UITableView *)tableView titleForHeaderInSection:(int)section {
186 return nil;
187 }
188
189 - (int) tableView:(UITableView *)tableView numberOfRowsInSection:(int)section {
190 return _themes.count;
191 }
192
193 - (id) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
194 UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ThemeCell"];
195 if (!cell) {
196 cell = [[[UITableViewCell alloc] initWithFrame:CGRectMake(0, 0, 100, 100) reuseIdentifier:@"ThemeCell"] autorelease];
197 //[cell setTableViewStyle:UITableViewCellStyleDefault];
198 }
199
200 NSDictionary *theme([_themes objectAtIndex:indexPath.row]);
201 cell.text = [theme objectForKey:@"Name"];
202 cell.hidesAccessoryWhenEditing = NO;
203 NSNumber *active([theme objectForKey:@"Active"]);
204 BOOL inactive(active == nil || ![active boolValue]);
205 [cell setImage:(inactive ? uncheckedImage : checkImage)];
206 return cell;
207 }
208
209 - (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
210 UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
211 NSMutableDictionary *theme = [_themes objectAtIndex:indexPath.row];
212 NSNumber *active = [theme objectForKey:@"Active"];
213 BOOL inactive = active == nil || ![active boolValue];
214 [theme setObject:[NSNumber numberWithBool:inactive] forKey:@"Active"];
215 [cell setImage:(!inactive ? uncheckedImage : checkImage)];
216 [tableView deselectRowAtIndexPath:(NSIndexPath *)indexPath animated:YES];
217 [self themesChanged];
218 }
219
220 - (void) tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {
221 NSUInteger fromIndex = [fromIndexPath row];
222 NSUInteger toIndex = [toIndexPath row];
223 if (fromIndex == toIndex)
224 return;
225 NSMutableDictionary *theme = [[[_themes objectAtIndex:fromIndex] retain] autorelease];
226 [_themes removeObjectAtIndex:fromIndex];
227 [_themes insertObject:theme atIndex:toIndex];
228 [self themesChanged];
229 }
230
231 - (UITableViewCellEditingStyle) tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
232 return UITableViewCellEditingStyleNone;
233 }
234
235 - (BOOL) tableView:(UITableView *)tableView shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath {
236 return NO;
237 }
238
239 - (BOOL) tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
240 return YES;
241 }
242 /* }}} */
243 @end
244 /* }}} */
245
246 @interface WBSettingsController: PSListController {
247 }
248
249 - (id) initForContentSize:(CGSize)size;
250 - (void) dealloc;
251 - (void) suspend;
252 - (void) navigationBarButtonClicked:(int)buttonIndex;
253 - (void) viewWillRedisplay;
254 - (void) pushController:(id)controller;
255 - (id) specifiers;
256 - (void) settingsChanged;
257 - (NSString *) title;
258 - (void) setPreferenceValue:(id)value specifier:(PSSpecifier *)spec;
259 - (id) readPreferenceValue:(PSSpecifier *)spec;
260
261 @end
262
263 @implementation WBSettingsController
264
265 - (id) initForContentSize:(CGSize)size {
266 if ((self = [super initForContentSize:size]) != nil) {
267 _plist = [[NSString stringWithFormat:@"%@/Library/Preferences/com.saurik.WinterBoard.plist", NSHomeDirectory()] retain];
268 _settings = [([NSMutableDictionary dictionaryWithContentsOfFile:_plist] ?: [NSMutableDictionary dictionary]) retain];
269 } return self;
270 }
271
272 - (void) dealloc {
273 [_settings release];
274 [_plist release];
275 [super dealloc];
276 }
277
278 - (void) suspend {
279 if (!settingsChanged)
280 return;
281
282 NSData *data([NSPropertyListSerialization dataFromPropertyList:_settings format:NSPropertyListBinaryFormat_v1_0 errorDescription:NULL]);
283 if (!data)
284 return;
285 if (![data writeToFile:_plist options:NSAtomicWrite error:NULL])
286 return;
287
288 unlink("/User/Library/Caches/com.apple.springboard-imagecache-icons");
289 unlink("/User/Library/Caches/com.apple.springboard-imagecache-icons.plist");
290 unlink("/User/Library/Caches/com.apple.springboard-imagecache-smallicons");
291 unlink("/User/Library/Caches/com.apple.springboard-imagecache-smallicons.plist");
292 system("killall SpringBoard");
293 }
294
295 - (void) navigationBarButtonClicked:(int)buttonIndex {
296 if (!settingsChanged) {
297 [super navigationBarButtonClicked:buttonIndex];
298 return;
299 }
300
301 if (buttonIndex == 0)
302 settingsChanged = NO;
303
304 [self suspend];
305 [self.rootController popController];
306 }
307
308 - (void) viewWillRedisplay {
309 if (settingsChanged)
310 [self settingsChanged];
311 [super viewWillRedisplay];
312 }
313
314 - (void) pushController:(id)controller {
315 [self hideNavigationBarButtons];
316 [super pushController:controller];
317 }
318
319 - (id) specifiers {
320 if (!_specifiers)
321 _specifiers = [[self loadSpecifiersFromPlistName:@"WinterBoard" target:self] retain];
322 return _specifiers;
323 }
324
325 - (void) settingsChanged {
326 [self showLeftButton:@"Respring" withStyle:2 rightButton:@"Cancel" withStyle:0];
327 settingsChanged = YES;
328 }
329
330 - (NSString *) title {
331 return @"WinterBoard";
332 }
333
334 - (void) setPreferenceValue:(id)value specifier:(PSSpecifier *)spec {
335 if ([[spec propertyForKey:@"negate"] boolValue])
336 value = [NSNumber numberWithBool:(![value boolValue])];
337 [_settings setValue:value forKey:[spec propertyForKey:@"key"]];
338 [self settingsChanged];
339 }
340
341 - (id) readPreferenceValue:(PSSpecifier *)spec {
342 NSString *key([spec propertyForKey:@"key"]);
343 id defaultValue([spec propertyForKey:@"default"]);
344 id plistValue([_settings objectForKey:key]);
345 if (!plistValue)
346 return defaultValue;
347 if ([[spec propertyForKey:@"negate"] boolValue])
348 plistValue = [NSNumber numberWithBool:(![plistValue boolValue])];
349 return plistValue;
350 }
351
352 @end