git.y1.nz

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.