2016-05-11 21:04:49 +00:00
//
// InsertDiskViewController . m
// Mini vMac
//
// Created by Jes ú s A . Á lvarez on 07 / 05 / 2016.
2018-04-28 10:12:43 +00:00
// Copyright © 2016 -2018 namedfork . All rights reserved .
2016-05-11 21:04:49 +00:00
//
# import "InsertDiskViewController.h"
# import "AppDelegate.h"
2016-05-21 13:01:57 +00:00
# import "UIImage+DiskImageIcon.h"
2016-05-11 21:04:49 +00:00
2022-05-26 17:11:38 +00:00
@ interface InsertDiskViewController ( ) < UITextFieldDelegate >
2016-05-11 21:04:49 +00:00
@ end
@ interface FileTableViewCell : UITableViewCell
@ property ( nonatomic , weak ) InsertDiskViewController * controller ;
@ property ( nonatomic , strong ) NSString * filePath ;
2016-06-04 13:13:08 +00:00
@ property ( nonatomic , assign ) BOOL showExtension ;
2016-05-11 21:04:49 +00:00
- ( void ) share : ( id ) sender ;
- ( void ) rename : ( id ) sender ;
- ( void ) delete : ( id ) sender ;
@ end
@ implementation InsertDiskViewController
{
NSString * basePath ;
NSArray < NSString * > * diskImages , * otherFiles ;
UIAlertController * createDiskImageController ;
__block __weak UITextField * sizeTextField ;
__block __weak UITextField * nameTextField ;
2016-06-03 21:33:26 +00:00
NSString * fileToRename ;
2016-06-04 13:13:08 +00:00
BOOL importing ;
2016-05-11 21:04:49 +00:00
}
+ ( void ) initialize {
UIMenuController * menuController = [ UIMenuController sharedMenuController ] ;
menuController . menuItems = @ [ [ [ UIMenuItem alloc ] initWithTitle : NSLocalizedString ( @ "Rename" , @ "Rename Context Menu Item" ) action : @ selector ( rename : ) ] ,
[ [ UIMenuItem alloc ] initWithTitle : NSLocalizedString ( @ "Share" , @ "Share Context Menu Item" ) action : @ selector ( share : ) ] ] ;
}
- ( void ) viewDidLoad {
[ super viewDidLoad ] ;
basePath = [ AppDelegate sharedInstance ] . documentsPath ;
[ self loadDirectoryContents ] ;
self . navigationItem . rightBarButtonItem = self . editButtonItem ;
}
2016-06-04 13:13:08 +00:00
- ( void ) viewWillAppear : ( BOOL ) animated {
[ super viewWillAppear : animated ] ;
importing = [ [ AppDelegate sharedEmulator ] . currentApplication isEqualToString : @ "ImportFl" ] ;
if ( importing ) {
self . title = NSLocalizedString ( @ "Import File" , nil ) ;
}
}
2016-05-11 21:04:49 +00:00
- ( void ) viewDidAppear : ( BOOL ) animated {
[ super viewDidAppear : animated ] ;
NSNotificationCenter * nc = [ NSNotificationCenter defaultCenter ] ;
2016-05-28 11:01:13 +00:00
[ nc addObserver : self selector : @ selector ( reloadData : ) name : [ AppDelegate sharedEmulator ] . insertDiskNotification object : nil ] ;
[ nc addObserver : self selector : @ selector ( reloadData : ) name : [ AppDelegate sharedEmulator ] . ejectDiskNotification object : nil ] ;
2016-06-06 17:42:10 +00:00
[ nc addObserver : self selector : @ selector ( reloadData : ) name : DocumentsChangedNotification object : nil ] ;
2016-12-04 19:05:24 +00:00
if ( [ UIApplication instancesRespondToSelector : @ selector ( btcMouseSetRawMode : ) ] ) {
[ [ UIApplication sharedApplication ] btcMouseSetRawMode : NO ] ;
}
2016-05-11 21:04:49 +00:00
}
- ( void ) viewDidDisappear : ( BOOL ) animated {
[ super viewDidDisappear : animated ] ;
NSNotificationCenter * nc = [ NSNotificationCenter defaultCenter ] ;
2016-05-28 11:01:13 +00:00
[ nc removeObserver : self name : [ AppDelegate sharedEmulator ] . insertDiskNotification object : nil ] ;
[ nc removeObserver : self name : [ AppDelegate sharedEmulator ] . ejectDiskNotification object : nil ] ;
2016-06-06 17:42:10 +00:00
[ nc removeObserver : self name : DocumentsChangedNotification object : nil ] ;
2016-05-11 21:04:49 +00:00
}
- ( void ) loadDirectoryContents {
NSArray * allFiles = [ [ NSFileManager defaultManager ] contentsOfDirectoryAtPath : basePath error : NULL ] ;
2016-05-26 19:07:54 +00:00
diskImages = [ allFiles filteredArrayUsingPredicate : [ NSPredicate predicateWithFormat : @ "%@ containsObject: pathExtension.lowercaseString" , [ AppDelegate sharedInstance ] . diskImageExtensions ] ] ;
2016-05-11 21:04:49 +00:00
otherFiles = [ allFiles filteredArrayUsingPredicate : [ NSPredicate predicateWithBlock : ^ BOOL ( NSString * _Nonnull name , NSDictionary < NSString * , id > * _Nullable bindings ) {
2020-09-30 20:40:32 +00:00
BOOL isDiskImage = [ self -> diskImages containsObject : name ] ;
2016-05-11 21:04:49 +00:00
BOOL isDirectory ;
BOOL isHidden = [ name hasPrefix : @ "." ] ;
2020-09-30 20:40:32 +00:00
[ [ NSFileManager defaultManager ] fileExistsAtPath : [ self -> basePath stringByAppendingPathComponent : name ] isDirectory : & isDirectory ] ;
2016-05-11 21:04:49 +00:00
return ! ( isDirectory || isDiskImage || isHidden ) ;
} ] ] ;
}
- ( void ) reloadData : ( id ) sender {
[ self loadDirectoryContents ] ;
[ self . tableView reloadData ] ;
}
2016-06-06 16:57:42 +00:00
- ( IBAction ) refresh : ( id ) sender {
[ self reloadData : sender ] ;
[ self . refreshControl endRefreshing ] ;
}
2016-05-25 18:49:56 +00:00
# pragma mark - Button Actions
2016-05-11 21:04:49 +00:00
- ( void ) showSettings : ( id ) sender {
[ [ AppDelegate sharedInstance ] showSettings : sender ] ;
}
- ( void ) dismiss : ( id ) sender {
[ self dismissViewControllerAnimated : YES completion : nil ] ;
}
2016-05-25 18:49:56 +00:00
- ( void ) macInterrupt : ( id ) sender {
[ self dismiss : sender ] ;
2016-05-28 11:01:13 +00:00
[ [ AppDelegate sharedEmulator ] interrupt ] ;
2016-05-25 18:49:56 +00:00
}
- ( void ) macReset : ( id ) sender {
2016-05-28 11:01:13 +00:00
[ [ AppDelegate sharedEmulator ] reset ] ;
2016-05-25 18:49:56 +00:00
}
2016-05-11 21:04:49 +00:00
# pragma mark - Table view data source
- ( NSInteger ) numberOfSectionsInTableView : ( UITableView * ) tableView {
2016-06-04 13:13:08 +00:00
return self . editing || importing ? 2 : 1 ;
2016-05-11 21:04:49 +00:00
}
- ( NSInteger ) tableView : ( UITableView * ) tableView numberOfRowsInSection : ( NSInteger ) section {
switch ( section ) {
2016-06-06 16:25:30 +00:00
case 0 : return diskImages . count + ( self . editing ? 1 : 0 ) ;
2016-05-11 21:04:49 +00:00
case 1 : return otherFiles . count ;
default : return 0 ;
}
}
- ( NSString * ) tableView : ( UITableView * ) tableView titleForHeaderInSection : ( NSInteger ) section {
return section = = 0 ? NSLocalizedString ( @ "Disk Images" , nil ) : NSLocalizedString ( @ "Other Files" , nil ) ;
}
- ( CGFloat ) tableView : ( UITableView * ) tableView heightForHeaderInSection : ( NSInteger ) section {
return self . editing ? UITableViewAutomaticDimension : 0.0 ;
}
- ( void ) setEditing : ( BOOL ) editing animated : ( BOOL ) animated {
[ super setEditing : editing animated : animated ] ;
[ self . tableView beginUpdates ] ;
[ self loadDirectoryContents ] ;
self . tableView . tableHeaderView = nil ;
[ self . tableView reloadSections : [ NSIndexSet indexSetWithIndex : 0 ] withRowAnimation : UITableViewRowAnimationFade ] ;
2016-06-04 13:28:47 +00:00
if ( importing ) {
[ self . tableView reloadSections : [ NSIndexSet indexSetWithIndex : 1 ] withRowAnimation : UITableViewRowAnimationFade ] ;
} else if ( editing ) {
2016-05-11 21:04:49 +00:00
[ self . tableView insertSections : [ NSIndexSet indexSetWithIndex : 1 ] withRowAnimation : UITableViewRowAnimationFade ] ;
} else {
[ self . tableView deleteSections : [ NSIndexSet indexSetWithIndex : 1 ] withRowAnimation : UITableViewRowAnimationFade ] ;
}
[ self . tableView endUpdates ] ;
}
- ( NSString * ) fileAtIndexPath : ( NSIndexPath * ) indexPath {
NSArray < NSString * > * files = indexPath . section = = 0 ? diskImages : otherFiles ;
if ( indexPath . row < files . count ) {
NSString * fileName = files [ indexPath . row ] ;
return [ basePath stringByAppendingPathComponent : fileName ] ;
} else {
return nil ;
}
}
- ( NSIndexPath * ) indexPathForFile : ( NSString * ) filePath {
NSString * fileName = filePath . lastPathComponent ;
if ( [ diskImages containsObject : fileName ] ) {
return [ NSIndexPath indexPathForRow : [ diskImages indexOfObject : fileName ] inSection : 0 ] ;
} else if ( [ otherFiles containsObject : fileName ] ) {
return [ NSIndexPath indexPathForRow : [ otherFiles indexOfObject : fileName ] inSection : 1 ] ;
} else {
return nil ;
}
}
- ( UITableViewCell * ) tableView : ( UITableView * ) tableView cellForRowAtIndexPath : ( NSIndexPath * ) indexPath {
FileTableViewCell * cell = [ tableView dequeueReusableCellWithIdentifier : @ "cell" forIndexPath : indexPath ] ;
NSString * filePath = [ self fileAtIndexPath : indexPath ] ;
if ( filePath ) {
2016-06-04 13:13:08 +00:00
cell . showExtension = self . editing || importing ;
2016-05-11 21:04:49 +00:00
cell . filePath = filePath ;
2016-05-28 11:01:13 +00:00
if ( [ [ AppDelegate sharedEmulator ] isDiskInserted : filePath ] ) {
2016-05-11 21:04:49 +00:00
cell . userInteractionEnabled = NO ;
cell . textLabel . enabled = NO ;
cell . imageView . alpha = 0.5 ;
}
} else {
cell . textLabel . text = NSLocalizedString ( @ "Create Disk Image…" , nil ) ;
cell . detailTextLabel . text = nil ;
}
cell . controller = self ;
return cell ;
}
- ( UITableViewCellEditingStyle ) tableView : ( UITableView * ) tableView editingStyleForRowAtIndexPath : ( NSIndexPath * ) indexPath {
if ( self . editing = = NO ) {
return UITableViewCellEditingStyleNone ;
} else if ( indexPath . section = = 0 ) {
if ( indexPath . row = = diskImages . count ) {
return UITableViewCellEditingStyleInsert ;
}
NSString * filePath = [ self fileAtIndexPath : indexPath ] ;
2016-05-28 11:01:13 +00:00
BOOL isInserted = [ [ AppDelegate sharedEmulator ] isDiskInserted : filePath ] ;
2016-05-11 21:04:49 +00:00
return isInserted ? UITableViewCellEditingStyleNone : UITableViewCellEditingStyleDelete ;
} else {
return UITableViewCellEditingStyleDelete ;
}
}
- ( void ) tableView : ( UITableView * ) tableView commitEditingStyle : ( UITableViewCellEditingStyle ) editingStyle forRowAtIndexPath : ( NSIndexPath * ) indexPath {
if ( editingStyle = = UITableViewCellEditingStyleDelete ) {
2016-05-26 21:23:49 +00:00
NSString * filePath = [ self fileAtIndexPath : indexPath ] ;
if ( [ UIAlertController class ] ) {
2022-05-26 18:24:05 +00:00
[ self askDeleteFile : filePath sourceView : [ tableView cellForRowAtIndexPath : indexPath ] ] ;
2016-05-26 21:23:49 +00:00
} else {
[ self deleteFile : filePath ] ;
}
2016-05-11 21:04:49 +00:00
} else if ( editingStyle = = UITableViewCellEditingStyleInsert ) {
[ self createDiskImage ] ;
}
}
- ( BOOL ) tableView : ( UITableView * ) tableView shouldShowMenuForRowAtIndexPath : ( nonnull NSIndexPath * ) indexPath {
2020-10-01 17:31:49 +00:00
if ( @ available ( iOS 13 , * ) ) {
return NO ;
}
2016-05-11 21:04:49 +00:00
return [ self fileAtIndexPath : indexPath ] ! = nil ;
}
- ( BOOL ) tableView : ( UITableView * ) tableView canPerformAction : ( SEL ) action forRowAtIndexPath : ( NSIndexPath * ) indexPath withSender : ( id ) sender {
2016-06-03 21:33:26 +00:00
return ( action = = @ selector ( share : ) || ( action = = @ selector ( rename : ) || ( [ UIAlertController class ] && action = = @ selector ( delete : ) ) ) ) ;
2016-05-11 21:04:49 +00:00
}
- ( void ) tableView : ( UITableView * ) tableView performAction : ( SEL ) action forRowAtIndexPath : ( NSIndexPath * ) indexPath withSender : ( id ) sender {
// menu will not be shown if this method doesn ' t exist
}
- ( void ) tableView : ( UITableView * ) tableView didSelectRowAtIndexPath : ( NSIndexPath * ) indexPath {
NSString * filePath = [ self fileAtIndexPath : indexPath ] ;
2022-05-26 17:11:38 +00:00
BOOL hadAnyDisksInserted = [ AppDelegate sharedEmulator ] . anyDiskInserted ;
2020-10-01 17:31:49 +00:00
if ( [ self insertDisk : filePath ] ) {
[ self dismissViewControllerAnimated : YES completion : nil ] ;
2022-05-26 17:11:38 +00:00
if ( ! hadAnyDisksInserted ) {
// Add to quick actions if it was booted from ( no disks inserted previously and still inserted after 10 s )
dispatch_after ( dispatch_time ( DISPATCH_TIME _NOW , ( int64_t ) ( 10.0 * NSEC_PER _SEC ) ) , dispatch_get _main _queue ( ) , ^ {
id < Emulator > emulator = [ AppDelegate sharedEmulator ] ;
if ( emulator . isRunning && [ emulator isDiskInserted : filePath ] ) {
[ self updateApplicationShortcutsWithDisk : filePath ] ;
}
} ) ;
}
}
}
# pragma mark - Quick Actions
- ( void ) updateApplicationShortcutsWithDisk : ( NSString * ) filePath {
// Update user defaults
NSUserDefaults * userDefaults = [ NSUserDefaults standardUserDefaults ] ;
NSString * fileName = filePath . lastPathComponent ;
NSMutableArray * recentDisks = [ userDefaults arrayForKey : @ "recentDisks" ] . mutableCopy ;
if ( [ recentDisks containsObject : fileName ] ) {
return ;
}
[ recentDisks insertObject : fileName atIndex : 0 ] ;
if ( recentDisks . count > 4 ) {
[ recentDisks removeObjectsInRange : NSMakeRange ( 4 , recentDisks . count - 4 ) ] ;
}
[ userDefaults setObject : recentDisks forKey : @ "recentDisks" ] ;
// Update quick actions
NSMutableArray * shortcutItems = [ NSMutableArray arrayWithCapacity : 4 ] ;
for ( NSString * diskName in recentDisks ) {
UIApplicationShortcutItem * shortcutItem = [ self shortcutItemForDisk : diskName ] ;
if ( shortcutItem ) {
[ shortcutItems addObject : shortcutItem ] ;
}
}
[ UIApplication sharedApplication ] . shortcutItems = shortcutItems ;
}
- ( nullable UIApplicationShortcutItem * ) shortcutItemForDisk : ( NSString * ) fileName {
BOOL isDiskImage = [ [ AppDelegate sharedInstance ] . diskImageExtensions containsObject : fileName . pathExtension . lowercaseString ] ;
if ( ! isDiskImage ) {
return nil ;
}
NSString * filePath = [ basePath stringByAppendingPathComponent : fileName ] ;
NSString * title = [ fileName stringByDeletingPathExtension ] ;
UIApplicationShortcutIcon * icon = nil ;
NSDictionary * attributes = [ [ NSURL fileURLWithPath : filePath ] resourceValuesForKeys : @ [ NSURLTotalFileSizeKey , NSURLFileSizeKey ] error : NULL ] ;
if ( attributes && attributes [ NSURLTotalFileSizeKey ] ) {
NSInteger fileSize = [ attributes [ NSURLTotalFileSizeKey ] integerValue ] ;
NSInteger numBlocks = fileSize / 512 ;
icon = [ UIApplicationShortcutIcon iconWithTemplateImageName : numBlocks = = 800 || numBlocks = = 1600 ? @ "floppy" : @ "floppyV" ] ;
} else {
return nil ;
2016-05-11 21:04:49 +00:00
}
2022-05-26 17:11:38 +00:00
return [ [ UIApplicationShortcutItem alloc ] initWithType : @ "disk" localizedTitle : title localizedSubtitle : nil icon : icon userInfo : @ { @ "disk" : fileName } ] ;
2016-05-11 21:04:49 +00:00
}
# pragma mark - File Actions
2020-10-01 17:31:49 +00:00
- ( BOOL ) insertDisk : ( NSString * ) filePath {
id < Emulator > emulator = [ AppDelegate sharedEmulator ] ;
if ( filePath && ! [ emulator isDiskInserted : filePath ] ) {
return [ emulator insertDisk : filePath ] ;
}
return NO ;
}
2016-05-26 21:23:49 +00:00
- ( void ) deleteFile : ( NSString * ) filePath {
NSError * error = nil ;
if ( [ [ NSFileManager defaultManager ] removeItemAtPath : filePath error : & error ] ) {
NSIndexPath * indexPath = [ self indexPathForFile : filePath ] ;
[ self loadDirectoryContents ] ;
[ self . tableView deleteRowsAtIndexPaths : @ [ indexPath ] withRowAnimation : UITableViewRowAnimationFade ] ;
} else {
[ [ AppDelegate sharedInstance ] showAlertWithTitle : NSLocalizedString ( @ "Could not delete file" , nil ) message : error . localizedDescription ] ;
}
}
2022-05-26 18:24:05 +00:00
- ( void ) askDeleteFile : ( NSString * ) filePath sourceView : ( UIView * ) sourceView {
2016-05-11 21:04:49 +00:00
NSString * fileName = filePath . lastPathComponent ;
NSString * message = [ NSString stringWithFormat : NSLocalizedString ( @ "Are you sure you want to delete %@? This operation cannot be undone." , nil ) , fileName ] ;
2020-10-01 17:35:22 +00:00
UIAlertController * alertController = [ UIAlertController alertControllerWithTitle : NSLocalizedString ( @ "Delete File" , nil ) message : message preferredStyle : UIAlertControllerStyleActionSheet ] ;
2016-06-04 10:36:36 +00:00
[ alertController addAction : [ UIAlertAction actionWithTitle : NSLocalizedString ( @ "Cancel" , nil ) style : UIAlertActionStyleCancel handler : nil ] ] ;
2016-05-11 21:04:49 +00:00
[ alertController addAction : [ UIAlertAction actionWithTitle : NSLocalizedString ( @ "Delete" , nil ) style : UIAlertActionStyleDestructive handler : ^ ( UIAlertAction * _Nonnull action ) {
2016-05-26 21:23:49 +00:00
[ self deleteFile : filePath ] ;
2016-05-11 21:04:49 +00:00
} ] ] ;
2022-05-26 18:24:05 +00:00
alertController . popoverPresentationController . sourceView = sourceView ;
2016-05-11 21:04:49 +00:00
[ self presentViewController : alertController animated : YES completion : nil ] ;
}
- ( void ) askRenameFile : ( NSString * ) filePath {
NSString * fileName = filePath . lastPathComponent ;
2020-09-30 20:40:32 +00:00
UIAlertController * alertController = [ UIAlertController alertControllerWithTitle : fileName message : NSLocalizedString ( @ "Enter new name" , nil ) preferredStyle : UIAlertControllerStyleAlert ] ;
[ alertController addTextFieldWithConfigurationHandler : ^ ( UITextField * _Nonnull textField ) {
self -> nameTextField = textField ;
textField . delegate = self ;
textField . placeholder = fileName ;
textField . text = fileName ;
} ] ;
[ alertController addAction : [ UIAlertAction actionWithTitle : NSLocalizedString ( @ "Cancel" , nil ) style : UIAlertActionStyleCancel handler : nil ] ] ;
[ alertController addAction : [ UIAlertAction actionWithTitle : NSLocalizedString ( @ "Rename" , nil ) style : UIAlertActionStyleDefault handler : ^ ( UIAlertAction * _Nonnull action ) {
NSString * newName = alertController . textFields . firstObject . text ;
[ self renameFile : filePath toName : newName ] ;
} ] ] ;
[ self presentViewController : alertController animated : YES completion : nil ] ;
2016-06-03 21:33:26 +00:00
}
- ( void ) renameFile : ( NSString * ) filePath toName : ( NSString * ) newName {
NSError * error = nil ;
NSString * newPath = [ filePath . stringByDeletingLastPathComponent stringByAppendingPathComponent : newName ] ;
if ( [ [ NSFileManager defaultManager ] moveItemAtPath : filePath toPath : newPath error : & error ] ) {
NSIndexPath * oldIndexPath = [ self indexPathForFile : filePath ] ;
[ self loadDirectoryContents ] ;
NSIndexPath * newIndexPath = [ self indexPathForFile : newPath ] ;
if ( newIndexPath = = nil || newIndexPath . section >= self . tableView . numberOfSections ) {
[ self . tableView deleteRowsAtIndexPaths : @ [ oldIndexPath ] withRowAnimation : UITableViewRowAnimationFade ] ;
2016-05-11 21:04:49 +00:00
} else {
2016-06-03 21:33:26 +00:00
[ self . tableView moveRowAtIndexPath : oldIndexPath toIndexPath : newIndexPath ] ;
[ self . tableView reloadRowsAtIndexPaths : @ [ newIndexPath ] withRowAnimation : UITableViewRowAnimationNone ] ;
2016-05-11 21:04:49 +00:00
}
2016-06-03 21:33:26 +00:00
} else {
[ [ AppDelegate sharedInstance ] showAlertWithTitle : NSLocalizedString ( @ "Could not rename file" , nil ) message : error . localizedDescription ] ;
}
2016-05-11 21:04:49 +00:00
}
# pragma mark - Disk Image Creation
- ( void ) createDiskImage {
2020-09-30 20:40:32 +00:00
UIAlertController * alertController = [ UIAlertController alertControllerWithTitle : NSLocalizedString ( @ "Create Disk Image" , nil ) message : nil preferredStyle : UIAlertControllerStyleAlert ] ;
[ alertController addTextFieldWithConfigurationHandler : ^ ( UITextField * _Nonnull textField ) {
textField . placeholder = NSLocalizedString ( @ "name" , nil ) ;
[ textField addTarget : self action : @ selector ( validateCreateDiskImageInput : ) forControlEvents : UIControlEventAllEditingEvents ] ;
} ] ;
[ alertController addTextFieldWithConfigurationHandler : ^ ( UITextField * _Nonnull textField ) {
[ self _configureNewDiskSizeField : textField ] ;
} ] ;
[ alertController addAction : [ UIAlertAction actionWithTitle : NSLocalizedString ( @ "Cancel" , nil ) style : UIAlertActionStyleCancel handler : nil ] ] ;
UIAlertAction * createAction = [ UIAlertAction actionWithTitle : NSLocalizedString ( @ "Create" , nil ) style : UIAlertActionStyleDefault handler : ^ ( UIAlertAction * _Nonnull action ) {
NSString * name = [ self _newDiskImageName ] ;
off_t size = [ self _newDiskImageSize ] ;
self -> createDiskImageController = nil ;
[ self createDiskImageWithName : name size : size ] ;
} ] ;
[ alertController addAction : createAction ] ;
createAction . enabled = NO ;
[ self presentViewController : alertController animated : YES completion : nil ] ;
createDiskImageController = alertController ;
2016-06-06 16:25:30 +00:00
}
- ( void ) _configureNewDiskSizeField : ( UITextField * ) textField {
textField . secureTextEntry = NO ;
textField . placeholder = NSLocalizedString ( @ "size" , nil ) ;
textField . keyboardType = UIKeyboardTypeDecimalPad ;
UILabel * unitLabel = [ [ UILabel alloc ] initWithFrame : CGRectMake ( 0 , 0 , 60.0 , 32.0 ) ] ;
textField . rightViewMode = UITextFieldViewModeAlways ;
textField . rightView = unitLabel ;
unitLabel . textAlignment = NSTextAlignmentRight ;
UISegmentedControl * unitsControl = [ [ UISegmentedControl alloc ] initWithFrame : CGRectMake ( 0 , 0 , 80.0 , 16.0 ) ] ;
NSArray * units = @ [ NSLocalizedString ( @ "KB" , nil ) , NSLocalizedString ( @ "MB" , nil ) ] ;
[ units enumerateObjectsUsingBlock : ^ ( NSString * title , NSUInteger idx , BOOL * _Nonnull stop ) {
[ unitsControl insertSegmentWithTitle : title atIndex : idx animated : NO ] ;
2016-05-11 21:04:49 +00:00
} ] ;
2016-06-06 16:25:30 +00:00
unitsControl . selectedSegmentIndex = 0 ;
textField . rightView = unitsControl ;
sizeTextField = textField ;
textField . delegate = self ;
[ textField addTarget : self action : @ selector ( validateCreateDiskImageInput : ) forControlEvents : UIControlEventAllEditingEvents ] ;
[ unitsControl addTarget : self action : @ selector ( validateCreateDiskImageInput : ) forControlEvents : UIControlEventValueChanged ] ;
unitLabel . text = [ unitsControl titleForSegmentAtIndex : unitsControl . selectedSegmentIndex ] ;
2016-05-11 21:04:49 +00:00
}
- ( BOOL ) validateCreateDiskImageInput : ( id ) sender {
BOOL valid = NO ;
2020-09-30 20:40:32 +00:00
if ( self . presentedViewController = = createDiskImageController ) {
2016-05-11 21:04:49 +00:00
NSString * name = [ self _newDiskImageName ] ;
BOOL nameIsValid = ( name . length > 0 ) && ! [ name hasPrefix : @ "." ] && ! [ name containsString : @ "/" ] && ! [ name containsString : @ "*" ] ;
off_t size = [ self _newDiskImageSize ] ;
BOOL sizeIsValid = ( size >= 400 * 1024 ) && ( size <= 2 LL * 1024 * 1024 * 1024 ) ;
valid = nameIsValid && sizeIsValid ;
2016-06-06 16:25:30 +00:00
if ( createDiskImageController ! = nil ) {
UIAlertAction * createAction = createDiskImageController . actions [ 1 ] ;
createAction . enabled = valid ;
} else if ( sender = = sizeTextField . rightView ) {
// fake edit event to call alertViewShouldEnableFirstOtherButton
[ sizeTextField sendActionsForControlEvents : UIControlEventEditingChanged ] ;
}
2016-05-11 21:04:49 +00:00
}
return valid ;
}
- ( NSString * ) _newDiskImageName {
2016-06-06 16:25:30 +00:00
if ( createDiskImageController ! = nil ) {
return createDiskImageController . textFields [ 0 ] . text ;
} else {
return nil ;
}
2016-05-11 21:04:49 +00:00
}
- ( off_t ) _newDiskImageSize {
2020-09-30 20:40:32 +00:00
if ( createDiskImageController = = nil ) {
2016-05-11 21:04:49 +00:00
return 0 ;
}
2016-06-06 16:25:30 +00:00
UISegmentedControl * unitsControl = ( UISegmentedControl * ) sizeTextField . rightView ;
2016-05-11 21:04:49 +00:00
off_t unitsMultiplier = ( unitsControl . selectedSegmentIndex = = 0 ) ? 1024 : 1024 * 1024 ;
2016-06-06 16:25:30 +00:00
off_t size = sizeTextField . text . floatValue * unitsMultiplier ;
2016-05-11 21:04:49 +00:00
return size ;
}
- ( void ) createDiskImageWithName : ( NSString * ) name size : ( off_t ) size {
NSString * imageFileName = [ [ AppDelegate sharedInstance ] . diskImageExtensions containsObject : name . pathExtension . lowercaseString ] ? name : [ name stringByAppendingPathExtension : @ "img" ] ;
NSString * imagePath = [ basePath stringByAppendingPathComponent : imageFileName ] ;
int fd = open ( imagePath . fileSystemRepresentation , O_CREAT | O_TRUNC | O_EXCL | O_WRONLY , 0666 ) ;
if ( fd = = -1 ) {
[ [ AppDelegate sharedInstance ] showAlertWithTitle : NSLocalizedString ( @ "Could not create disk image" , nil ) message : [ [ NSString alloc ] initWithUTF8String : strerror ( errno ) ] ] ;
return ;
}
2020-10-01 17:31:49 +00:00
[ self showFileActivityIndicatorWithTitle : NSLocalizedString ( @ "Creating Disk Image" , nil ) completion : ^ ( UIActivityIndicatorView * activityIndicatorView ) {
[ self _writeNewDiskImage : fd size : size activityIndicator : activityIndicatorView ] ;
} ] ;
}
- ( void ) showFileActivityIndicatorWithTitle : ( NSString * ) title completion : ( void ( ^ _Nonnull ) ( UIActivityIndicatorView * activityIndicatorView ) ) completionBlock {
2024-02-09 15:06:37 +00:00
UIActivityIndicatorView * activityIndicatorView = [ [ UIActivityIndicatorView alloc ] initWithActivityIndicatorStyle : UIActivityIndicatorViewStyleLarge ] ;
2020-10-01 16:53:06 +00:00
if ( @ available ( iOS 13 , * ) ) {
activityIndicatorView . color = [ UIColor labelColor ] ;
} else {
activityIndicatorView . color = [ UIColor blackColor ] ;
}
2020-10-01 17:31:49 +00:00
UIAlertController * alertController = [ UIAlertController alertControllerWithTitle : title message : @ "\n\n\n" preferredStyle : UIAlertControllerStyleAlert ] ;
2020-09-30 20:40:32 +00:00
[ self presentViewController : alertController animated : true completion : ^ {
UIView * alertView = alertController . view ;
activityIndicatorView . autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin ;
activityIndicatorView . center = CGPointMake ( alertView . bounds . size . width / 2.0 , alertView . bounds . size . height / 2.0 + 32.0 ) ;
2016-06-06 18:49:48 +00:00
[ alertView addSubview : activityIndicatorView ] ;
[ activityIndicatorView startAnimating ] ;
2020-10-01 17:31:49 +00:00
completionBlock ( activityIndicatorView ) ;
2020-09-30 20:40:32 +00:00
} ] ;
2016-06-06 18:49:48 +00:00
}
2020-10-01 17:31:49 +00:00
- ( void ) dismissFileActivityIndicator : ( UIActivityIndicatorView * ) activityIndicatorView withErrorTitle : ( NSString * ) errorTitle message : ( NSString * ) errorMessage {
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
[ activityIndicatorView stopAnimating ] ;
[ self dismissViewControllerAnimated : YES completion : ^ {
if ( errorTitle ) {
[ [ AppDelegate sharedInstance ] showAlertWithTitle : errorTitle message : errorMessage ] ;
}
} ] ;
[ self . tableView beginUpdates ] ;
[ self loadDirectoryContents ] ;
[ self . tableView reloadSections : [ NSIndexSet indexSetWithIndex : 0 ] withRowAnimation : UITableViewRowAnimationFade ] ;
[ self . tableView endUpdates ] ;
} ) ;
}
2020-09-30 20:40:32 +00:00
- ( void ) _writeNewDiskImage : ( int ) fd size : ( off_t ) size activityIndicator : ( UIActivityIndicatorView * ) activityIndicatorView {
dispatch_async ( dispatch_get _global _queue ( QOS_CLASS _USER _INITIATED , 0 ) , ^ {
2016-06-06 18:49:48 +00:00
int error = 0 ;
if ( ftruncate ( fd , size ) ) {
error = errno ;
}
close ( fd ) ;
2020-10-01 17:31:49 +00:00
[ self dismissFileActivityIndicator : activityIndicatorView
withErrorTitle : error ? NSLocalizedString ( @ "Could not create disk image" , nil ) : nil
message : error ? @ ( strerror ( error ) ) : nil ] ;
} ) ;
}
- ( NSString * ) nameForDuplicatingPath : ( NSString * ) filePath {
NSFileManager * fileManager = [ NSFileManager defaultManager ] ;
NSString * fileName = filePath . lastPathComponent ;
NSString * copyPath = [ NSString stringWithFormat : @ "%@/%@ copy.%@" , filePath . stringByDeletingLastPathComponent , fileName . lastPathComponent . stringByDeletingPathExtension , fileName . pathExtension ] ;
for ( int copyNumber = 2 ; [ fileManager fileExistsAtPath : copyPath ] ; copyNumber + + ) {
copyPath = [ NSString stringWithFormat : @ "%@/%@ copy %d.%@" , filePath . stringByDeletingLastPathComponent , fileName . lastPathComponent . stringByDeletingPathExtension , copyNumber , fileName . pathExtension ] ;
}
return copyPath ;
}
- ( void ) duplicateDiskImage : ( NSString * ) filePath {
[ self showFileActivityIndicatorWithTitle : NSLocalizedString ( @ "Duplicating disk image" , nil ) completion : ^ ( UIActivityIndicatorView * activityIndicatorView ) {
[ self _duplicateDiskImage : filePath activityIndicator : activityIndicatorView ] ;
} ] ;
}
- ( void ) _duplicateDiskImage : ( NSString * ) filePath activityIndicator : ( UIActivityIndicatorView * ) activityIndicatorView {
dispatch_async ( dispatch_get _global _queue ( QOS_CLASS _USER _INITIATED , 0 ) , ^ {
NSError * error = nil ;
[ [ NSFileManager defaultManager ] copyItemAtPath : filePath toPath : [ self nameForDuplicatingPath : filePath ] error : & error ] ;
2016-06-06 18:49:48 +00:00
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
[ activityIndicatorView stopAnimating ] ;
2020-09-30 20:40:32 +00:00
[ self dismissViewControllerAnimated : YES completion : ^ {
2016-06-06 18:49:48 +00:00
if ( error ) {
2020-10-01 17:31:49 +00:00
[ [ AppDelegate sharedInstance ] showAlertWithTitle : NSLocalizedString ( @ "Could not duplicate disk image" , nil ) message : error . localizedDescription ] ;
2016-06-06 18:49:48 +00:00
}
2020-09-30 20:40:32 +00:00
} ] ;
2016-06-06 18:49:48 +00:00
[ self . tableView beginUpdates ] ;
[ self loadDirectoryContents ] ;
[ self . tableView reloadSections : [ NSIndexSet indexSetWithIndex : 0 ] withRowAnimation : UITableViewRowAnimationFade ] ;
[ self . tableView endUpdates ] ;
2016-05-11 21:04:49 +00:00
} ) ;
2016-06-06 18:49:48 +00:00
} ) ;
2016-05-11 21:04:49 +00:00
}
2020-10-01 17:31:49 +00:00
2016-05-11 21:04:49 +00:00
# pragma mark - Text Field Delegate
- ( BOOL ) textField : ( UITextField * ) textField shouldChangeCharactersInRange : ( NSRange ) range replacementString : ( NSString * ) string {
if ( textField = = sizeTextField ) {
UISegmentedControl * unitsControl = ( UISegmentedControl * ) textField . rightView ;
NSArray * unitShortcuts = @ [ @ "k" , @ "m" ] ;
if ( string . length = = 0 ) {
return YES ;
} else if ( string . length = = 1 && [ unitShortcuts indexOfObject : string . lowercaseString ] ! = NSNotFound ) {
unitsControl . selectedSegmentIndex = [ unitShortcuts indexOfObject : string . lowercaseString ] ;
[ unitsControl sendActionsForControlEvents : UIControlEventValueChanged ] ;
return NO ;
} else {
NSString * newString = [ textField . text stringByReplacingCharactersInRange : range withString : string ] ;
NSScanner * scanner = [ NSScanner scannerWithString : newString ] ;
double value ;
return [ scanner scanDouble : & value ] && scanner . isAtEnd && value >= 0 ;
}
} else {
return YES ;
}
}
- ( void ) textFieldDidBeginEditing : ( UITextField * ) textField {
if ( textField = = nameTextField && textField . text . pathExtension . length > 0 ) {
UITextPosition * beforeExtensionPosition = [ textField positionFromPosition : textField . endOfDocument offset : - ( textField . text . pathExtension . length + 1 ) ] ;
UITextRange * nameWithoutExtensionRange = [ textField textRangeFromPosition : textField . beginningOfDocument toPosition : beforeExtensionPosition ] ;
[ textField performSelector : @ selector ( setSelectedTextRange : ) withObject : nameWithoutExtensionRange afterDelay : 0.1 ] ;
}
}
2020-10-01 17:31:49 +00:00
# pragma mark - Context Menu
- ( UIContextMenuConfiguration * ) tableView : ( UITableView * ) tableView contextMenuConfigurationForRowAtIndexPath : ( NSIndexPath * ) indexPath point : ( CGPoint ) point API_AVAILABLE ( ios ( 13.0 ) ) {
FileTableViewCell * cell = [ tableView cellForRowAtIndexPath : indexPath ] ;
if ( cell . filePath = = nil ) {
return nil ;
}
UIAction * insertAction = [ UIAction actionWithTitle : @ "Insert" image : [ UIImage systemImageNamed : @ "play.fill" ] identifier : nil handler : ^ ( __kindof UIAction * _Nonnull action ) {
[ self insertDisk : cell . filePath ] ;
} ] ;
UIAction * deleteAction = [ UIAction actionWithTitle : @ "Delete" image : [ UIImage systemImageNamed : @ "trash" ] identifier : nil handler : ^ ( __kindof UIAction * _Nonnull action ) {
[ cell delete : self ] ;
} ] ;
deleteAction . attributes = UIMenuElementAttributesDestructive ;
if ( [ [ AppDelegate sharedEmulator ] isDiskInserted : cell . filePath ] ) {
insertAction . attributes = UIMenuElementAttributesDisabled ;
deleteAction . attributes = UIMenuElementAttributesDisabled ;
}
NSArray < UIAction * > * actions = @ [
insertAction ,
[ UIAction actionWithTitle : @ "Rename" image : [ UIImage systemImageNamed : @ "pencil" ] identifier : nil handler : ^ ( __kindof UIAction * _Nonnull action ) {
[ cell rename : self ] ;
} ] ,
[ UIAction actionWithTitle : @ "Duplicate" image : [ UIImage systemImageNamed : @ "plus.square.on.square" ] identifier : nil handler : ^ ( __kindof UIAction * _Nonnull action ) {
[ self duplicateDiskImage : cell . filePath ] ;
} ] ,
[ UIAction actionWithTitle : @ "Share" image : [ UIImage systemImageNamed : @ "square.and.arrow.up" ] identifier : nil handler : ^ ( __kindof UIAction * _Nonnull action ) {
[ cell share : self ] ;
} ] ,
deleteAction
] ;
return [ UIContextMenuConfiguration configurationWithIdentifier : nil previewProvider : nil actionProvider : ^ UIMenu * _Nullable ( NSArray < UIMenuElement * > * _Nonnull suggestedActions ) {
return [ UIMenu menuWithTitle : @ "" children : actions ] ;
} ] ;
}
2016-05-11 21:04:49 +00:00
@ end
@ implementation FileTableViewCell
2016-07-02 21:06:09 +00:00
{
UIImage * defaultIcon ;
}
2016-05-11 21:04:49 +00:00
- ( void ) prepareForReuse {
[ super prepareForReuse ] ;
self . userInteractionEnabled = YES ;
self . textLabel . text = nil ;
self . textLabel . enabled = YES ;
self . imageView . image = nil ;
self . imageView . alpha = 1.0 ;
self . detailTextLabel . text = nil ;
2016-07-02 21:06:09 +00:00
if ( _filePath ) {
[ [ NSNotificationCenter defaultCenter ] removeObserver : self name : DidUpdateIconForDiskImageNotification object : _filePath ] ;
_filePath = nil ;
}
2016-05-11 21:04:49 +00:00
}
- ( void ) setFilePath : ( NSString * ) filePath {
2016-07-02 21:06:09 +00:00
NSNotificationCenter * notificationCenter = [ NSNotificationCenter defaultCenter ] ;
if ( _filePath ) {
[ notificationCenter removeObserver : self name : DidUpdateIconForDiskImageNotification object : _filePath ] ;
}
2016-05-11 21:04:49 +00:00
_filePath = filePath ;
2016-07-02 21:06:09 +00:00
if ( _filePath ) {
[ notificationCenter addObserver : self selector : @ selector ( didUpdateDiskIcon : ) name : DidUpdateIconForDiskImageNotification object : _filePath ] ;
}
2016-05-11 21:04:49 +00:00
NSString * fileName = filePath . lastPathComponent ;
2016-06-04 13:13:08 +00:00
self . textLabel . text = self . showExtension ? fileName : fileName . stringByDeletingPathExtension ;
2016-06-06 17:29:53 +00:00
NSDictionary * attributes = [ [ NSURL fileURLWithPath : filePath ] resourceValuesForKeys : @ [ NSURLTotalFileSizeKey , NSURLFileSizeKey ] error : NULL ] ;
2016-05-11 21:04:49 +00:00
if ( attributes && attributes [ NSURLTotalFileSizeKey ] ) {
BOOL isDiskImage = [ [ AppDelegate sharedInstance ] . diskImageExtensions containsObject : fileName . pathExtension . lowercaseString ] ;
2016-05-21 13:01:57 +00:00
if ( isDiskImage ) {
2016-07-02 21:06:09 +00:00
NSInteger fileSize = [ attributes [ NSURLTotalFileSizeKey ] integerValue ] ;
NSInteger numBlocks = fileSize / 512 ;
defaultIcon = [ UIImage imageNamed : numBlocks = = 800 || numBlocks = = 1600 ? @ "floppy" : @ "floppyV" ] ;
self . imageView . image = [ UIImage imageWithIconForDiskImage : filePath ] ? : defaultIcon ;
2016-05-21 13:01:57 +00:00
} else {
2016-07-02 21:06:09 +00:00
defaultIcon = [ UIImage imageNamed : @ "document" ] ;
self . imageView . image = defaultIcon ;
2016-05-21 13:01:57 +00:00
}
2016-05-11 21:04:49 +00:00
NSString * sizeString = [ NSByteCountFormatter stringFromByteCount : [ attributes [ NSURLTotalFileSizeKey ] longLongValue ] countStyle : NSByteCountFormatterCountStyleBinary ] ;
self . detailTextLabel . text = sizeString ;
} else {
self . imageView . image = nil ;
self . detailTextLabel . text = nil ;
}
}
2016-07-02 21:06:09 +00:00
- ( void ) didUpdateDiskIcon : ( NSNotification * ) notification {
if ( [ _filePath isEqual : notification . object ] ) {
UIImage * icon = notification . userInfo [ @ "icon" ] ;
self . imageView . image = icon ? : defaultIcon ;
}
}
2016-05-11 21:04:49 +00:00
- ( void ) share : ( id ) sender {
UIActivityViewController * avc = [ [ UIActivityViewController alloc ] initWithActivityItems : @ [ [ NSURL fileURLWithPath : self . filePath ] ] applicationActivities : nil ] ;
2016-05-26 21:23:49 +00:00
if ( [ avc respondsToSelector : @ selector ( popoverPresentationController ) ] ) {
avc . modalPresentationStyle = UIModalPresentationPopover ;
avc . popoverPresentationController . sourceView = self . imageView ;
avc . popoverPresentationController . sourceRect = self . imageView . bounds ;
}
2016-05-11 21:04:49 +00:00
[ self . controller presentViewController : avc animated : YES completion : nil ] ;
}
- ( void ) rename : ( id ) sender {
[ self . controller askRenameFile : self . filePath ] ;
}
- ( void ) delete : ( id ) sender {
2022-05-26 18:24:05 +00:00
[ self . controller askDeleteFile : self . filePath sourceView : self ] ;
2016-05-11 21:04:49 +00:00
}
@ end