SameBoy | Accurate GB/GBC emulator |
| download: https://git.y1.nz/archives/sameboy.tar.gz | |
| README | Files | Log | Refs | LICENSE |
iOS/GBROMViewController.m
1 #import "GBROMViewController.h"
2 #import "GBROMManager.h"
3 #import "GBViewController.h"
4 #import "GBLibraryViewController.h"
5 #import <CoreServices/CoreServices.h>
6 #import <objc/runtime.h>
7
8 @implementation GBROMViewController
9 {
10 NSIndexPath *_renamingPath;
11 NSArray *_roms;
12 __weak UIAlertController *_alertToRemove;
13 }
14
15 - (instancetype)init
16 {
17 self = [super initWithStyle:UITableViewStyleGrouped];
18 self.navigationItem.rightBarButtonItem = self.editButtonItem;
19
20 [[NSNotificationCenter defaultCenter] addObserver:self
21 selector:@selector(reactivate)
22 name:UIApplicationDidBecomeActiveNotification
23 object:nil];
24 return self;
25 }
26
27 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
28 {
29 return 2;
30 }
31
32 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
33 {
34 if (section == 1) return 2;
35 return (_roms = [GBROMManager sharedManager].allROMs).count;
36 }
37
38 - (UITableViewCell *)cellForROM:(NSString *)rom
39 {
40 UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
41 cell.textLabel.text = rom.lastPathComponent;
42 bool isCurrentROM = [rom isEqualToString:[GBROMManager sharedManager].currentROM];
43 bool checkmark = isCurrentROM;
44 cell.accessoryType = checkmark? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone;
45
46 NSString *pngPath = [[[GBROMManager sharedManager] autosaveStateFileForROM:rom] stringByAppendingPathExtension:@"png"];
47 UIGraphicsBeginImageContextWithOptions((CGSize){60, 60}, false, self.view.window.screen.scale);
48 UIBezierPath *mask = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 3, 60, 54) cornerRadius:4];
49 [mask addClip];
50 UIImage *image = [UIImage imageWithContentsOfFile:pngPath];
51 [image drawInRect:mask.bounds];
52 if (@available(iOS 13.0, *)) {
53 [[UIColor tertiaryLabelColor] set];
54 }
55 else {
56 [[UIColor colorWithWhite:0 alpha:0.5] set];
57 }
58 [mask stroke];
59 cell.imageView.image = UIGraphicsGetImageFromCurrentImageContext();
60 UIGraphicsEndImageContext();
61
62 return cell;
63
64 }
65
66 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
67 {
68 if (indexPath.section == 1) {
69 UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
70 switch (indexPath.item) {
71 case 0: cell.textLabel.text = @"Import ROM files"; break;
72 case 1: cell.textLabel.text = @"Show Library in Files"; break;
73 }
74 return cell;
75 }
76 return [self cellForROM:_roms[[indexPath indexAtPosition:1]]];
77 }
78
79 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
80 {
81 if (indexPath.section == 1) return [super tableView:tableView heightForRowAtIndexPath:indexPath];
82
83 return 60;
84 }
85
86 - (NSString *)title
87 {
88 return @"Local Library";
89 }
90
91 - (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section
92 {
93 if (section == 0) return nil;
94
95 return @"You can also import ROM files by opening them in SameBoy using the Files app or a web browser, or by sending them over with AirDrop.";
96 }
97
98 - (void)romSelectedAtIndex:(unsigned)index
99 {
100 NSString *rom = _roms[index];
101 [GBROMManager sharedManager].currentROM = rom;
102 [self.presentingViewController dismissViewControllerAnimated:true completion:nil];
103 }
104
105 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
106 {
107 if (indexPath.section == 1) {
108 switch (indexPath.item) {
109 case 0: {
110 UIViewController *parent = self.presentingViewController;
111 NSString *gbUTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)@"gb", NULL);
112 NSString *gbcUTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)@"gbc", NULL);
113 NSString *isxUTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)@"isx", NULL);
114 NSString *zipUTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)@"zip", NULL);
115
116 NSMutableSet *extensions = [NSMutableSet set];
117 [extensions addObjectsFromArray:(__bridge_transfer NSArray *)UTTypeCopyAllTagsWithClass((__bridge CFStringRef)gbUTI, kUTTagClassFilenameExtension)];
118 [extensions addObjectsFromArray:(__bridge_transfer NSArray *)UTTypeCopyAllTagsWithClass((__bridge CFStringRef)gbcUTI, kUTTagClassFilenameExtension)];
119 [extensions addObjectsFromArray:(__bridge_transfer NSArray *)UTTypeCopyAllTagsWithClass((__bridge CFStringRef)isxUTI, kUTTagClassFilenameExtension)];
120
121 if (extensions.count != 3) {
122 if (![[NSUserDefaults standardUserDefaults] boolForKey:@"GBShownUTIWarning"]) {
123 UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"File Association Conflict"
124 message:@"Due to a limitation in iOS, the file picker will allow you to select files not supported by SameBoy. SameBoy will only import GB, GBC and ISX files.\n\nIf you have a multi-system emulator installed, updating it could fix this problem."
125 preferredStyle:UIAlertControllerStyleAlert];
126 [alert addAction:[UIAlertAction actionWithTitle:@"Close"
127 style:UIAlertActionStyleCancel
128 handler:^(UIAlertAction *action) {
129 [[NSUserDefaults standardUserDefaults] setBool:true forKey:@"GBShownUTIWarning"];
130 [self tableView:tableView didSelectRowAtIndexPath:indexPath];
131 }]];
132 [self presentViewController:alert animated:true completion:nil];
133 return;
134 }
135 }
136
137 [self.presentingViewController dismissViewControllerAnimated:true completion:^{
138 UIDocumentPickerViewController *picker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[@"com.github.liji32.sameboy.gb",
139 @"com.github.liji32.sameboy.gbc",
140 @"com.github.liji32.sameboy.isx",
141 gbUTI ?: @"",
142 gbcUTI ?: @"",
143 isxUTI ?: @"",
144 zipUTI ?: @""]
145 inMode:UIDocumentPickerModeImport];
146 picker.allowsMultipleSelection = true;
147 if (@available(iOS 13.0, *)) {
148 picker.shouldShowFileExtensions = true;
149 }
150 picker.delegate = self;
151 objc_setAssociatedObject(picker, @selector(delegate), self, OBJC_ASSOCIATION_RETAIN);
152 [parent presentViewController:picker animated:true completion:nil];
153 }];
154 return;
155 }
156 case 1: {
157 NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"shareddocuments://%@",
158 [self.rootPath stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLPathAllowedCharacterSet]]]];
159 [[UIApplication sharedApplication] openURL:url
160 options:nil
161 completionHandler:nil];
162 return;
163 }
164 }
165 }
166 [self romSelectedAtIndex:indexPath.row];
167 }
168
169 - (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray <NSURL *>*)urls
170 {
171 [(GBViewController *)[UIApplication sharedApplication].delegate handleOpenURLs:urls
172 openInPlace:false];
173 }
174
175 - (UIModalPresentationStyle)modalPresentationStyle
176 {
177 return UIModalPresentationOverFullScreen;
178 }
179
180 - (void)deleteROMAtIndex:(unsigned)index
181 {
182 NSString *rom = _roms[index];
183
184 [[GBROMManager sharedManager] deleteROM:rom];
185 [self.tableView deleteRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:index inSection:0]] withRowAnimation:UITableViewRowAnimationAutomatic];
186 if ([[GBROMManager sharedManager].currentROM isEqualToString:rom]) {
187 [GBROMManager sharedManager].currentROM = nil;
188 }
189 }
190
191 - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
192 {
193 if (indexPath.section == 1) return;
194
195 if (editingStyle != UITableViewCellEditingStyleDelete) return;
196 NSString *rom = [self.tableView cellForRowAtIndexPath:indexPath].textLabel.text;
197 UIAlertController *alert = [UIAlertController alertControllerWithTitle:[NSString stringWithFormat:@"Delete “%@”?", rom]
198 message: @"Save data for this ROM will also be deleted."
199 preferredStyle:UIAlertControllerStyleAlert];
200 [alert addAction:[UIAlertAction actionWithTitle:@"Delete"
201 style:UIAlertActionStyleDestructive
202 handler:^(UIAlertAction *action) {
203 [self deleteROMAtIndex:indexPath.row];
204 }]];
205 [alert addAction:[UIAlertAction actionWithTitle:@"Cancel"
206 style:UIAlertActionStyleCancel
207 handler:nil]];
208 _alertToRemove = alert; // indexPath becoomes invalid if we reload, dismiss the alert if it happens
209 [self presentViewController:alert animated:true completion:nil];
210 }
211
212 - (void)renameRow:(NSIndexPath *)indexPath
213 {
214 if (indexPath.section == 1) return;
215
216 UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
217 CGRect frame = cell.textLabel.frame;
218 frame.size.width = cell.textLabel.superview.frame.size.width - 8 - frame.origin.x;
219 UITextField *field = [[UITextField alloc] initWithFrame:frame];
220 field.font = cell.textLabel.font;
221 field.text = cell.textLabel.text;
222 cell.textLabel.textColor = [UIColor clearColor];
223 [[cell.textLabel superview] addSubview:field];
224 [field becomeFirstResponder];
225 [field selectAll:nil];
226 _renamingPath = indexPath;
227 [field addTarget:self action:@selector(doneRename:) forControlEvents:UIControlEventEditingDidEnd | UIControlEventEditingDidEndOnExit];
228 }
229
230 - (void)renameROM:(NSString *)oldName toName:(NSString *)newName
231 {
232 [[GBROMManager sharedManager] renameROM:oldName toName:newName];
233 [self.tableView reloadData];
234 }
235
236 - (void)doneRename:(UITextField *)sender
237 {
238 if (!_renamingPath) return;
239 NSString *newName = sender.text;
240 NSString *oldName = [self.tableView cellForRowAtIndexPath:_renamingPath].textLabel.text;
241
242 _renamingPath = nil;
243 if ([newName isEqualToString:oldName]) {
244 [self.tableView reloadData];
245 return;
246 }
247
248 if ([newName containsString:@"/"]) {
249 [self.tableView reloadData];
250 UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Invalid Name"
251 message:@"You can't use a name that contains “/”. Please choose another name."
252 preferredStyle:UIAlertControllerStyleAlert];
253 [alert addAction:[UIAlertAction actionWithTitle:@"OK"
254 style:UIAlertActionStyleCancel
255 handler:nil]];
256 [self presentViewController:alert animated:true completion:nil];
257 return;
258 }
259 if ([[GBROMManager sharedManager].forbiddenNames containsObject:newName]) {
260 [self.tableView reloadData];
261 UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Invalid Name"
262 message:@"This name is reserved by SameBoy or iOS. Please choose another name."
263 preferredStyle:UIAlertControllerStyleAlert];
264 [alert addAction:[UIAlertAction actionWithTitle:@"OK"
265 style:UIAlertActionStyleCancel
266 handler:nil]];
267 [self presentViewController:alert animated:true completion:nil];
268 return;
269 }
270 [self renameROM:oldName toName:newName];
271 _renamingPath = nil;
272 }
273
274 - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
275 {
276 return indexPath.section == 0;
277 }
278
279 - (void)duplicateROMAtIndex:(unsigned)index
280 {
281 [[GBROMManager sharedManager] duplicateROM:_roms[index]];
282 [self.tableView reloadData];
283 }
284
285
286 // Leave these ROM management to iOS 13.0 and up for now
287 - (UIContextMenuConfiguration *)tableView:(UITableView *)tableView
288 contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath
289 point:(CGPoint)point API_AVAILABLE(ios(13.0))
290 {
291 if (indexPath.section == 1) return nil;
292
293 return [UIContextMenuConfiguration configurationWithIdentifier:nil
294 previewProvider:nil
295 actionProvider:^UIMenu *(NSArray<UIMenuElement *> *suggestedActions) {
296 UIAction *deleteAction = [UIAction actionWithTitle:@"Delete"
297 image:[UIImage systemImageNamed:@"trash"]
298 identifier:nil
299 handler:^(UIAction *action) {
300 [self tableView:tableView
301 commitEditingStyle:UITableViewCellEditingStyleDelete
302 forRowAtIndexPath:indexPath];
303 }];
304 deleteAction.attributes = UIMenuElementAttributesDestructive;
305 NSMutableArray *items = @[
306 [UIAction actionWithTitle:@"Rename"
307 image:[UIImage systemImageNamed:@"pencil"]
308 identifier:nil
309 handler:^(__kindof UIAction *action) {
310 [self renameRow:indexPath];
311 }],
312 [UIAction actionWithTitle:@"Duplicate"
313 image:[UIImage systemImageNamed:@"plus.square.on.square"]
314 identifier:nil
315 handler:^(__kindof UIAction *action) {
316 [self duplicateROMAtIndex:indexPath.row];
317 }],
318 ].mutableCopy;
319 [items addObject:deleteAction];
320 return [UIMenu menuWithTitle:nil children:items];
321 }];
322 }
323
324 - (void)deselectRow
325 {
326 if (self.tableView.indexPathForSelectedRow) {
327 [self.tableView deselectRowAtIndexPath:self.tableView.indexPathForSelectedRow animated:true];
328 }
329 }
330
331 - (void)reactivate
332 {
333 [self deselectRow];
334 // Do not auto-reload if busy
335 if (self.view.window.userInteractionEnabled) {
336 [self.tableView reloadData];
337 if (self.presentedViewController == _alertToRemove) {
338 [self dismissViewControllerAnimated:true completion:nil];
339 }
340 }
341 }
342
343 - (void)viewWillAppear:(BOOL)animated
344 {
345 [self.tableView reloadData];
346 [super viewWillAppear:animated];
347 }
348
349 - (NSString *)rootPath
350 {
351 return [GBROMManager sharedManager].localRoot;
352 }
353
354 @end
This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.