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