mirror of https://github.com/AxioDL/amuse.git
866 lines
32 KiB
Plaintext
866 lines
32 KiB
Plaintext
#include "AudioGroupFilePresenter.hpp"
|
|
#include <athena/FileReader.hpp>
|
|
#include <amuse/AudioGroupProject.hpp>
|
|
#import "AmuseContainingApp.hpp"
|
|
#import "AudioUnitBackend.hpp"
|
|
#import "AudioUnitViewController.hpp"
|
|
|
|
static std::string StrToLower(const std::string& str)
|
|
{
|
|
std::string ret = str;
|
|
std::transform(ret.begin(), ret.end(), ret.begin(), tolower);
|
|
return ret;
|
|
}
|
|
|
|
@implementation AudioGroupDataToken
|
|
- (id)initWithDataCollection:(AudioGroupDataCollection *)collection
|
|
{
|
|
self = [super init];
|
|
m_collection = collection;
|
|
return self;
|
|
}
|
|
@end
|
|
|
|
@implementation AudioGroupCollectionToken
|
|
- (id)initWithCollection:(AudioGroupCollection *)collection
|
|
{
|
|
self = [super init];
|
|
m_collection = collection;
|
|
return self;
|
|
}
|
|
@end
|
|
|
|
@implementation AudioGroupSFXToken
|
|
- (id)initWithName:(NSAttributedString*)name loadId:(int)loadId sfx:(const amuse::SFXGroupIndex::SFXEntry*)sfx
|
|
{
|
|
self = [super init];
|
|
m_name = name;
|
|
m_loadId = loadId;
|
|
m_sfx = sfx;
|
|
return self;
|
|
}
|
|
@end
|
|
|
|
@implementation AudioGroupSampleToken
|
|
- (id)initWithName:(NSAttributedString*)name samp:(const std::pair<amuse::AudioGroupSampleDirectory::Entry,
|
|
amuse::AudioGroupSampleDirectory::ADPCMParms>*)sample
|
|
{
|
|
self = [super init];
|
|
m_name = name;
|
|
m_sample = sample;
|
|
return self;
|
|
}
|
|
@end
|
|
|
|
@implementation AudioGroupToken
|
|
- (id)initWithName:(NSString*)name id:(int)gid songGroup:(const amuse::SongGroupIndex*)group
|
|
{
|
|
self = [super init];
|
|
m_name = name;
|
|
m_song = group;
|
|
m_id = gid;
|
|
return self;
|
|
}
|
|
- (id)initWithName:(NSString*)name id:(int)gid sfxGroup:(const amuse::SFXGroupIndex*)group
|
|
{
|
|
self = [super init];
|
|
m_name = name;
|
|
m_sfx = group;
|
|
m_id = gid;
|
|
return self;
|
|
}
|
|
@end
|
|
|
|
@implementation AudioGroupFilePresenter
|
|
|
|
- (NSURL*)presentedItemURL
|
|
{
|
|
return m_groupURL;
|
|
}
|
|
|
|
- (NSOperationQueue*)presentedItemOperationQueue
|
|
{
|
|
return [NSOperationQueue mainQueue];
|
|
}
|
|
|
|
AudioGroupCollection::AudioGroupCollection(NSURL* url)
|
|
: m_url(url), m_token([[AudioGroupCollectionToken alloc] initWithCollection:this]) {}
|
|
|
|
void AudioGroupCollection::addCollection(AudioGroupFilePresenter* presenter,
|
|
std::vector<std::pair<std::string, amuse::IntrusiveAudioGroupData>>&& collection)
|
|
{
|
|
for (std::pair<std::string, amuse::IntrusiveAudioGroupData>& pair : collection)
|
|
{
|
|
NSURL* collectionUrl = [m_url URLByAppendingPathComponent:@(pair.first.c_str())];
|
|
|
|
amuse::IntrusiveAudioGroupData& dataIn = pair.second;
|
|
auto search = m_groups.find(pair.first);
|
|
if (search == m_groups.end())
|
|
{
|
|
search = m_groups.emplace(pair.first,
|
|
std::make_unique<AudioGroupDataCollection>(pair.first,
|
|
[collectionUrl URLByAppendingPathComponent:@"proj"],
|
|
[collectionUrl URLByAppendingPathComponent:@"pool"],
|
|
[collectionUrl URLByAppendingPathComponent:@"sdir"],
|
|
[collectionUrl URLByAppendingPathComponent:@"samp"],
|
|
[collectionUrl URLByAppendingPathComponent:@"meta"])).first;
|
|
}
|
|
|
|
AudioGroupDataCollection& dataCollection = *search->second;
|
|
dataCollection.m_projData.resize(dataIn.getProjSize());
|
|
memmove(dataCollection.m_projData.data(), dataIn.getProj(), dataIn.getProjSize());
|
|
|
|
dataCollection.m_poolData.resize(dataIn.getPoolSize());
|
|
memmove(dataCollection.m_poolData.data(), dataIn.getPool(), dataIn.getPoolSize());
|
|
|
|
dataCollection.m_sdirData.resize(dataIn.getSdirSize());
|
|
memmove(dataCollection.m_sdirData.data(), dataIn.getSdir(), dataIn.getSdirSize());
|
|
|
|
dataCollection.m_sampData.resize(dataIn.getSampSize());
|
|
memmove(dataCollection.m_sampData.data(), dataIn.getSamp(), dataIn.getSampSize());
|
|
|
|
dataCollection.m_metaData.emplace(dataIn.getDataFormat(), dataIn.getAbsoluteProjOffsets(), true);
|
|
dataCollection._indexData(presenter);
|
|
}
|
|
}
|
|
|
|
void AudioGroupCollection::update(AudioGroupFilePresenter* presenter)
|
|
{
|
|
NSFileManager* fman = [NSFileManager defaultManager];
|
|
NSArray<NSURL*>* contents =
|
|
[fman contentsOfDirectoryAtURL:m_url
|
|
includingPropertiesForKeys:@[NSURLIsDirectoryKey]
|
|
options:NSDirectoryEnumerationSkipsSubdirectoryDescendants |
|
|
NSDirectoryEnumerationSkipsHiddenFiles
|
|
error:nil];
|
|
if (!contents)
|
|
return;
|
|
|
|
for (NSURL* path in contents)
|
|
{
|
|
NSNumber* isDir;
|
|
[path getResourceValue:&isDir forKey:NSURLIsDirectoryKey error:nil];
|
|
|
|
if (isDir.boolValue)
|
|
{
|
|
auto search = m_groups.find(path.lastPathComponent.UTF8String);
|
|
if (search == m_groups.end())
|
|
{
|
|
std::string nameStr = path.lastPathComponent.UTF8String;
|
|
search =
|
|
m_groups.emplace(nameStr,
|
|
std::make_unique<AudioGroupDataCollection>(nameStr,
|
|
[path URLByAppendingPathComponent:@"proj"],
|
|
[path URLByAppendingPathComponent:@"pool"],
|
|
[path URLByAppendingPathComponent:@"sdir"],
|
|
[path URLByAppendingPathComponent:@"samp"],
|
|
[path URLByAppendingPathComponent:@"meta"])).first;
|
|
search->second->_attemptLoad(presenter);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool AudioGroupCollection::doSearch(std::string_view str)
|
|
{
|
|
bool ret = false;
|
|
m_filterGroups.clear();
|
|
m_filterGroups.reserve(m_groups.size());
|
|
for (auto it = m_groups.begin() ; it != m_groups.end() ; ++it)
|
|
if (str.empty() || StrToLower(it->first).find(str.data()) != std::string::npos)
|
|
{
|
|
m_filterGroups.push_back(it);
|
|
ret = true;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
bool AudioGroupCollection::doActiveFilter()
|
|
{
|
|
bool ret = false;
|
|
m_filterGroups.clear();
|
|
m_filterGroups.reserve(m_groups.size());
|
|
for (auto it = m_groups.begin() ; it != m_groups.end() ; ++it)
|
|
if (it->second->m_metaData->active)
|
|
{
|
|
m_filterGroups.push_back(it);
|
|
ret = true;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void AudioGroupCollection::addSFX(std::vector<AudioGroupSFXToken*>& vecOut)
|
|
{
|
|
for (auto it = m_groups.begin() ; it != m_groups.end() ; ++it)
|
|
{
|
|
if (!it->second->m_metaData->active)
|
|
continue;
|
|
const auto& sfxGroups = it->second->m_loadedGroup->getProj().sfxGroups();
|
|
std::map<int, const amuse::SFXGroupIndex*> sortGroups;
|
|
for (const auto& pair : sfxGroups)
|
|
sortGroups[pair.first] = &pair.second;
|
|
for (const auto& pair : sortGroups)
|
|
{
|
|
NSMutableAttributedString* name = [[NSMutableAttributedString alloc] initWithString:m_url.lastPathComponent attributes:@{NSForegroundColorAttributeName: [NSColor grayColor]}];
|
|
[name appendAttributedString:[[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@" %s (%d)", it->first.c_str(), pair.first]]];
|
|
vecOut.push_back([[AudioGroupSFXToken alloc] initWithName:name loadId:0 sfx:nil]);
|
|
std::map<int, const amuse::SFXGroupIndex::SFXEntry*> sortSfx;
|
|
for (const auto& pair : pair.second->m_sfxEntries)
|
|
sortSfx[pair.first] = pair.second;
|
|
for (const auto& sfx : sortSfx)
|
|
{
|
|
name = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%d", sfx.first]];
|
|
vecOut.push_back([[AudioGroupSFXToken alloc] initWithName:name loadId:sfx.first sfx:sfx.second]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void AudioGroupCollection::addSamples(std::vector<AudioGroupSampleToken*>& vecOut)
|
|
{
|
|
for (auto it = m_groups.begin() ; it != m_groups.end() ; ++it)
|
|
{
|
|
if (!it->second->m_metaData->active)
|
|
continue;
|
|
const auto& samps = it->second->m_loadedGroup->getSdir().sampleEntries();
|
|
std::map<int, const std::pair<amuse::AudioGroupSampleDirectory::Entry, amuse::AudioGroupSampleDirectory::ADPCMParms>*> sortSamps;
|
|
for (const auto& pair : samps)
|
|
sortSamps[pair.first] = &pair.second;
|
|
|
|
NSMutableAttributedString* name = [[NSMutableAttributedString alloc] initWithString:m_url.lastPathComponent attributes:@{NSForegroundColorAttributeName: [NSColor grayColor]}];
|
|
[name appendAttributedString:[[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@" %s", it->first.c_str()]]];
|
|
vecOut.push_back([[AudioGroupSampleToken alloc] initWithName:name samp:nil]);
|
|
|
|
for (const auto& pair : sortSamps)
|
|
{
|
|
name = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%d", pair.first]];
|
|
vecOut.push_back([[AudioGroupSampleToken alloc] initWithName:name samp:pair.second]);
|
|
}
|
|
}
|
|
}
|
|
|
|
AudioGroupDataCollection::AudioGroupDataCollection(std::string_view name, NSURL* proj, NSURL* pool,
|
|
NSURL* sdir, NSURL* samp, NSURL* meta)
|
|
: m_name(name), m_proj(proj), m_pool(pool), m_sdir(sdir), m_samp(samp), m_meta(meta),
|
|
m_token([[AudioGroupDataToken alloc] initWithDataCollection:this]) {}
|
|
|
|
bool AudioGroupDataCollection::_attemptLoad(AudioGroupFilePresenter* presenter)
|
|
{
|
|
if (m_metaData && m_loadedData && m_loadedGroup)
|
|
return true;
|
|
if (!loadProj(presenter))
|
|
return false;
|
|
if (!loadPool(presenter))
|
|
return false;
|
|
if (!loadSdir(presenter))
|
|
return false;
|
|
if (!loadSamp(presenter))
|
|
return false;
|
|
if (!loadMeta(presenter))
|
|
return false;
|
|
|
|
return _indexData(presenter);
|
|
}
|
|
|
|
bool AudioGroupDataCollection::_indexData(AudioGroupFilePresenter* presenter)
|
|
{
|
|
amuse::Engine& engine = [presenter->m_audioGroupClient getAmuseEngine];
|
|
|
|
switch (m_metaData->fmt)
|
|
{
|
|
case amuse::DataFormat::GCN:
|
|
default:
|
|
m_loadedData.emplace(m_projData.data(), m_projData.size(),
|
|
m_poolData.data(), m_poolData.size(),
|
|
m_sdirData.data(), m_sdirData.size(),
|
|
m_sampData.data(), m_sampData.size(),
|
|
amuse::GCNDataTag{});
|
|
break;
|
|
case amuse::DataFormat::N64:
|
|
m_loadedData.emplace(m_projData.data(), m_projData.size(),
|
|
m_poolData.data(), m_poolData.size(),
|
|
m_sdirData.data(), m_sdirData.size(),
|
|
m_sampData.data(), m_sampData.size(),
|
|
m_metaData->absOffs, amuse::N64DataTag{});
|
|
break;
|
|
case amuse::DataFormat::PC:
|
|
m_loadedData.emplace(m_projData.data(), m_projData.size(),
|
|
m_poolData.data(), m_poolData.size(),
|
|
m_sdirData.data(), m_sdirData.size(),
|
|
m_sampData.data(), m_sampData.size(),
|
|
m_metaData->absOffs, amuse::PCDataTag{});
|
|
break;
|
|
}
|
|
|
|
m_loadedGroup = engine.addAudioGroup(*m_loadedData);
|
|
m_groupTokens.clear();
|
|
if (m_loadedGroup)
|
|
{
|
|
m_groupTokens.reserve(m_loadedGroup->getProj().songGroups().size() +
|
|
m_loadedGroup->getProj().sfxGroups().size());
|
|
|
|
{
|
|
const auto& songGroups = m_loadedGroup->getProj().songGroups();
|
|
std::map<int, const amuse::SongGroupIndex*> sortGroups;
|
|
for (const auto& pair : songGroups)
|
|
sortGroups[pair.first] = &pair.second;
|
|
for (const auto& pair : sortGroups)
|
|
{
|
|
NSString* name = [NSString stringWithFormat:@"%d", pair.first];
|
|
m_groupTokens.push_back([[AudioGroupToken alloc] initWithName:name id:pair.first
|
|
songGroup:pair.second]);
|
|
}
|
|
}
|
|
{
|
|
const auto& sfxGroups = m_loadedGroup->getProj().sfxGroups();
|
|
std::map<int, const amuse::SFXGroupIndex*> sortGroups;
|
|
for (const auto& pair : sfxGroups)
|
|
sortGroups[pair.first] = &pair.second;
|
|
for (const auto& pair : sortGroups)
|
|
{
|
|
NSString* name = [NSString stringWithFormat:@"%d", pair.first];
|
|
m_groupTokens.push_back([[AudioGroupToken alloc] initWithName:name id:pair.first
|
|
sfxGroup:pair.second]);
|
|
}
|
|
}
|
|
}
|
|
|
|
return m_loadedData && m_loadedGroup;
|
|
}
|
|
|
|
void AudioGroupDataCollection::enable(AudioGroupFilePresenter* presenter)
|
|
{
|
|
m_metaData->active = true;
|
|
NSFileCoordinator* coord = [[NSFileCoordinator alloc] initWithFilePresenter:presenter];
|
|
|
|
[coord coordinateWritingItemAtURL:m_meta options:0 error:nil
|
|
byAccessor:^(NSURL* newUrl)
|
|
{
|
|
FILE* fp = fopen(newUrl.path.UTF8String, "wb");
|
|
if (fp)
|
|
{
|
|
fwrite(&*m_metaData, 1, sizeof(*m_metaData), fp);
|
|
fclose(fp);
|
|
}
|
|
}];
|
|
}
|
|
|
|
void AudioGroupDataCollection::disable(AudioGroupFilePresenter* presenter)
|
|
{
|
|
m_metaData->active = false;
|
|
NSFileCoordinator* coord = [[NSFileCoordinator alloc] initWithFilePresenter:presenter];
|
|
|
|
[coord coordinateWritingItemAtURL:m_meta options:0 error:nil
|
|
byAccessor:^(NSURL* newUrl)
|
|
{
|
|
FILE* fp = fopen(newUrl.path.UTF8String, "wb");
|
|
if (fp)
|
|
{
|
|
fwrite(&*m_metaData, 1, sizeof(*m_metaData), fp);
|
|
fclose(fp);
|
|
}
|
|
}];
|
|
}
|
|
|
|
void AudioGroupDataCollection::moveURL(NSURL* oldUrl, NSURL* newUrl)
|
|
{
|
|
if (m_proj)
|
|
{
|
|
if ([m_proj isEqual:oldUrl])
|
|
m_proj = newUrl;
|
|
}
|
|
if (m_pool)
|
|
{
|
|
if ([m_pool isEqual:oldUrl])
|
|
m_pool = newUrl;
|
|
}
|
|
if (m_sdir)
|
|
{
|
|
if ([m_sdir isEqual:oldUrl])
|
|
m_sdir = newUrl;
|
|
}
|
|
if (m_samp)
|
|
{
|
|
if ([m_samp isEqual:oldUrl])
|
|
m_samp = newUrl;
|
|
}
|
|
}
|
|
|
|
bool AudioGroupDataCollection::loadProj(AudioGroupFilePresenter* presenter)
|
|
{
|
|
if (!m_proj)
|
|
return false;
|
|
NSFileCoordinator* coord = [[NSFileCoordinator alloc] initWithFilePresenter:presenter];
|
|
if (!coord)
|
|
return false;
|
|
NSError* err;
|
|
__block std::vector<uint8_t>& ret = m_projData;
|
|
[coord coordinateReadingItemAtURL:m_proj
|
|
options:NSFileCoordinatorReadingResolvesSymbolicLink error:&err
|
|
byAccessor:^(NSURL* newUrl)
|
|
{
|
|
athena::io::FileReader r([[newUrl path] UTF8String], 1024 * 32, false);
|
|
if (r.hasError())
|
|
return;
|
|
size_t len = r.length();
|
|
ret.resize(len);
|
|
r.readUBytesToBuf(ret.data(), len);
|
|
}];
|
|
return ret.size();
|
|
}
|
|
|
|
bool AudioGroupDataCollection::loadPool(AudioGroupFilePresenter* presenter)
|
|
{
|
|
if (!m_pool)
|
|
return false;
|
|
NSFileCoordinator* coord = [[NSFileCoordinator alloc] initWithFilePresenter:presenter];
|
|
if (!coord)
|
|
return false;
|
|
NSError* err;
|
|
__block std::vector<uint8_t>& ret = m_poolData;
|
|
[coord coordinateReadingItemAtURL:m_pool
|
|
options:NSFileCoordinatorReadingResolvesSymbolicLink error:&err
|
|
byAccessor:^(NSURL* newUrl)
|
|
{
|
|
athena::io::FileReader r([[newUrl path] UTF8String], 1024 * 32, false);
|
|
if (r.hasError())
|
|
return;
|
|
size_t len = r.length();
|
|
ret.resize(len);
|
|
r.readUBytesToBuf(ret.data(), len);
|
|
}];
|
|
return ret.size();
|
|
}
|
|
|
|
bool AudioGroupDataCollection::loadSdir(AudioGroupFilePresenter* presenter)
|
|
{
|
|
if (!m_sdir)
|
|
return false;
|
|
NSFileCoordinator* coord = [[NSFileCoordinator alloc] initWithFilePresenter:presenter];
|
|
if (!coord)
|
|
return false;
|
|
NSError* err;
|
|
__block std::vector<uint8_t>& ret = m_sdirData;
|
|
[coord coordinateReadingItemAtURL:m_sdir
|
|
options:NSFileCoordinatorReadingResolvesSymbolicLink error:&err
|
|
byAccessor:^(NSURL* newUrl)
|
|
{
|
|
athena::io::FileReader r([[newUrl path] UTF8String], 1024 * 32, false);
|
|
if (r.hasError())
|
|
return;
|
|
size_t len = r.length();
|
|
ret.resize(len);
|
|
r.readUBytesToBuf(ret.data(), len);
|
|
}];
|
|
return ret.size();
|
|
}
|
|
|
|
bool AudioGroupDataCollection::loadSamp(AudioGroupFilePresenter* presenter)
|
|
{
|
|
if (!m_samp)
|
|
return false;
|
|
NSFileCoordinator* coord = [[NSFileCoordinator alloc] initWithFilePresenter:presenter];
|
|
if (!coord)
|
|
return false;
|
|
NSError* err;
|
|
__block std::vector<uint8_t>& ret = m_sampData;
|
|
[coord coordinateReadingItemAtURL:m_samp
|
|
options:NSFileCoordinatorReadingResolvesSymbolicLink error:&err
|
|
byAccessor:^(NSURL* newUrl)
|
|
{
|
|
athena::io::FileReader r([[newUrl path] UTF8String], 1024 * 32, false);
|
|
if (r.hasError())
|
|
return;
|
|
size_t len = r.length();
|
|
ret.resize(len);
|
|
r.readUBytesToBuf(ret.data(), len);
|
|
}];
|
|
return ret.size();
|
|
}
|
|
|
|
bool AudioGroupDataCollection::loadMeta(AudioGroupFilePresenter* presenter)
|
|
{
|
|
if (!m_meta)
|
|
return false;
|
|
NSFileCoordinator* coord = [[NSFileCoordinator alloc] initWithFilePresenter:presenter];
|
|
if (!coord)
|
|
return false;
|
|
NSError* err;
|
|
__block std::experimental::optional<MetaData>& ret = m_metaData;
|
|
[coord coordinateReadingItemAtURL:m_meta
|
|
options:NSFileCoordinatorReadingResolvesSymbolicLink error:&err
|
|
byAccessor:^(NSURL* newUrl)
|
|
{
|
|
athena::io::FileReader r([[newUrl path] UTF8String], 1024 * 32, false);
|
|
if (r.hasError())
|
|
return;
|
|
ret.emplace(r);
|
|
}];
|
|
return ret.operator bool();
|
|
}
|
|
|
|
- (void)presentedSubitemDidChangeAtURL:(NSURL *)url
|
|
{
|
|
size_t relComps = url.pathComponents.count - m_groupURL.pathComponents.count;
|
|
if (relComps <= 1)
|
|
[self update];
|
|
}
|
|
|
|
- (void)presentedSubitemAtURL:(NSURL*)oldUrl didMoveToURL:(NSURL*)newUrl
|
|
{
|
|
for (auto& pair : m_audioGroupCollections)
|
|
{
|
|
for (auto& pair2 : pair.second->m_groups)
|
|
{
|
|
pair2.second->moveURL(oldUrl, newUrl);
|
|
}
|
|
}
|
|
}
|
|
|
|
- (NSInteger)outlineView:(NSOutlineView*)outlineView numberOfChildrenOfItem:(nullable id)item
|
|
{
|
|
m_lastOutlineView = outlineView;
|
|
if (!item)
|
|
return m_filterAudioGroupCollections.size();
|
|
|
|
AudioGroupCollection& collection = *((AudioGroupCollectionToken*)item)->m_collection;
|
|
return collection.m_filterGroups.size();
|
|
}
|
|
|
|
- (id)outlineView:(NSOutlineView*)outlineView child:(NSInteger)index ofItem:(nullable id)item
|
|
{
|
|
if (!item)
|
|
{
|
|
if (index >= m_filterAudioGroupCollections.size())
|
|
return nil;
|
|
return m_filterAudioGroupCollections[index]->second->m_token;
|
|
}
|
|
|
|
AudioGroupCollection& collection = *((AudioGroupCollectionToken*)item)->m_collection;
|
|
if (index >= collection.m_filterGroups.size())
|
|
return nil;
|
|
return collection.m_filterGroups[index]->second->m_token;
|
|
}
|
|
|
|
- (BOOL)outlineView:(NSOutlineView*)outlineView isItemExpandable:(id)item
|
|
{
|
|
if ([item isKindOfClass:[AudioGroupCollectionToken class]])
|
|
return YES;
|
|
return NO;
|
|
}
|
|
|
|
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
|
|
{
|
|
if ([item isKindOfClass:[AudioGroupCollectionToken class]])
|
|
{
|
|
AudioGroupCollection& collection = *((AudioGroupCollectionToken*)item)->m_collection;
|
|
if ([tableColumn.identifier isEqualToString:@"CollectionColumn"])
|
|
{
|
|
size_t totalOn = 0;
|
|
for (auto& pair : collection.m_groups)
|
|
if (pair.second->m_metaData->active)
|
|
++totalOn;
|
|
if (totalOn == 0)
|
|
return [NSNumber numberWithInt:NSOffState];
|
|
else if (totalOn == collection.m_groups.size())
|
|
return [NSNumber numberWithInt:NSOnState];
|
|
else
|
|
return [NSNumber numberWithInt:NSMixedState];
|
|
}
|
|
else if ([tableColumn.identifier isEqualToString:@"DetailsColumn"])
|
|
return [NSString stringWithFormat:@"%zu Group File%s",
|
|
collection.m_groups.size(),
|
|
collection.m_groups.size() > 1 ? "s" : ""];
|
|
}
|
|
else if ([item isKindOfClass:[AudioGroupDataToken class]])
|
|
{
|
|
AudioGroupDataCollection& data = *((AudioGroupDataToken*)item)->m_collection;
|
|
if ([tableColumn.identifier isEqualToString:@"CollectionColumn"])
|
|
return [NSNumber numberWithInt:data.m_metaData->active ? NSOnState : NSOffState];
|
|
else if ([tableColumn.identifier isEqualToString:@"DetailsColumn"])
|
|
{
|
|
if (!data.m_loadedGroup)
|
|
return @"";
|
|
if (data.m_loadedGroup->getProj().songGroups().size() && data.m_loadedGroup->getProj().sfxGroups().size())
|
|
return [NSString stringWithFormat:@"%zu Song Group%s, %zu SFX Group%s",
|
|
data.m_loadedGroup->getProj().songGroups().size(),
|
|
data.m_loadedGroup->getProj().songGroups().size() > 1 ? "s" : "",
|
|
data.m_loadedGroup->getProj().sfxGroups().size(),
|
|
data.m_loadedGroup->getProj().sfxGroups().size() > 1 ? "s" : ""];
|
|
else if (data.m_loadedGroup->getProj().songGroups().size())
|
|
return [NSString stringWithFormat:@"%zu Song Group%s",
|
|
data.m_loadedGroup->getProj().songGroups().size(),
|
|
data.m_loadedGroup->getProj().songGroups().size() > 1 ? "s" : ""];
|
|
else if (data.m_loadedGroup->getProj().sfxGroups().size())
|
|
return [NSString stringWithFormat:@"%zu SFX Group%s",
|
|
data.m_loadedGroup->getProj().sfxGroups().size(),
|
|
data.m_loadedGroup->getProj().sfxGroups().size() > 1 ? "s" : ""];
|
|
else
|
|
return @"";
|
|
}
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
- (void)outlineView:(NSOutlineView *)outlineView setObjectValue:(nullable id)object forTableColumn:(nullable NSTableColumn *)tableColumn byItem:(nullable id)item
|
|
{
|
|
bool dirty = false;
|
|
|
|
if ([item isKindOfClass:[AudioGroupCollectionToken class]])
|
|
{
|
|
AudioGroupCollection& collection = *((AudioGroupCollectionToken*)item)->m_collection;
|
|
if ([tableColumn.identifier isEqualToString:@"CollectionColumn"])
|
|
{
|
|
NSInteger active = [object integerValue];
|
|
if (active)
|
|
for (auto& pair : collection.m_groups)
|
|
pair.second->enable(self);
|
|
else
|
|
for (auto& pair : collection.m_groups)
|
|
pair.second->disable(self);
|
|
dirty = true;
|
|
}
|
|
}
|
|
else if ([item isKindOfClass:[AudioGroupDataToken class]])
|
|
{
|
|
AudioGroupDataCollection& data = *((AudioGroupDataToken*)item)->m_collection;
|
|
if ([tableColumn.identifier isEqualToString:@"CollectionColumn"])
|
|
{
|
|
NSInteger active = [object integerValue];
|
|
if (active)
|
|
data.enable(self);
|
|
else
|
|
data.disable(self);
|
|
dirty = true;
|
|
}
|
|
}
|
|
|
|
if (dirty)
|
|
[self resetIterators];
|
|
}
|
|
|
|
- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(nonnull id)cell forTableColumn:(nullable NSTableColumn *)tableColumn item:(nonnull id)item
|
|
{
|
|
if ([item isKindOfClass:[AudioGroupCollectionToken class]])
|
|
{
|
|
AudioGroupCollection& collection = *((AudioGroupCollectionToken*)item)->m_collection;
|
|
if ([tableColumn.identifier isEqualToString:@"CollectionColumn"])
|
|
((NSButtonCell*)cell).title = collection.m_url.lastPathComponent;
|
|
}
|
|
else if ([item isKindOfClass:[AudioGroupDataToken class]])
|
|
{
|
|
AudioGroupDataCollection& data = *((AudioGroupDataToken*)item)->m_collection;
|
|
if ([tableColumn.identifier isEqualToString:@"CollectionColumn"])
|
|
((NSButtonCell*)cell).title = @(data.m_name.c_str());
|
|
}
|
|
}
|
|
|
|
- (void)outlineViewSelectionDidChange:(NSNotification *)notification
|
|
{
|
|
DataOutlineView* ov = notification.object;
|
|
id item = [ov itemAtRow:ov.selectedRow];
|
|
[(AppDelegate*)NSApp.delegate outlineView:ov selectionChanged:item];
|
|
}
|
|
|
|
- (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id<NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
|
|
{
|
|
[outlineView setDropItem:nil dropChildIndex:NSOutlineViewDropOnItemIndex];
|
|
NSPasteboard* pboard = [info draggingPasteboard];
|
|
if ([[pboard types] containsObject:NSURLPboardType])
|
|
return NSDragOperationCopy;
|
|
return NSDragOperationNone;
|
|
}
|
|
|
|
- (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id<NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index
|
|
{
|
|
NSPasteboard* pboard = [info draggingPasteboard];
|
|
if ([[pboard types] containsObject:NSURLPboardType])
|
|
{
|
|
NSURL* url = [NSURL URLFromPasteboard:pboard];
|
|
[(AppDelegate*)NSApp.delegate importURL:url];
|
|
return YES;
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
- (BOOL)addCollectionName:(std::string&&)name items:(std::vector<std::pair<std::string, amuse::IntrusiveAudioGroupData>>&&)collection
|
|
{
|
|
NSFileCoordinator* coord = [[NSFileCoordinator alloc] initWithFilePresenter:self];
|
|
if (!coord)
|
|
return false;
|
|
|
|
NSURL* dir = [m_groupURL URLByAppendingPathComponent:@(name.c_str())];
|
|
__block AudioGroupCollection& insert = *m_audioGroupCollections.emplace(name, std::make_unique<AudioGroupCollection>(dir)).first->second;
|
|
insert.addCollection(self, std::move(collection));
|
|
|
|
[coord coordinateWritingItemAtURL:m_groupURL options:0 error:nil
|
|
byAccessor:^(NSURL* newUrl)
|
|
{
|
|
for (std::pair<const std::string, std::unique_ptr<AudioGroupDataCollection>>& pair : insert.m_groups)
|
|
{
|
|
NSURL* collectionUrl = [insert.m_url URLByAppendingPathComponent:@(pair.first.c_str())];
|
|
[[NSFileManager defaultManager] createDirectoryAtURL:collectionUrl withIntermediateDirectories:YES attributes:nil error:nil];
|
|
|
|
FILE* fp = fopen(pair.second->m_proj.path.UTF8String, "wb");
|
|
if (fp)
|
|
{
|
|
fwrite(pair.second->m_projData.data(), 1, pair.second->m_projData.size(), fp);
|
|
fclose(fp);
|
|
}
|
|
|
|
fp = fopen(pair.second->m_pool.path.UTF8String, "wb");
|
|
if (fp)
|
|
{
|
|
fwrite(pair.second->m_poolData.data(), 1, pair.second->m_poolData.size(), fp);
|
|
fclose(fp);
|
|
}
|
|
|
|
fp = fopen(pair.second->m_sdir.path.UTF8String, "wb");
|
|
if (fp)
|
|
{
|
|
fwrite(pair.second->m_sdirData.data(), 1, pair.second->m_sdirData.size(), fp);
|
|
fclose(fp);
|
|
}
|
|
|
|
fp = fopen(pair.second->m_samp.path.UTF8String, "wb");
|
|
if (fp)
|
|
{
|
|
fwrite(pair.second->m_sampData.data(), 1, pair.second->m_sampData.size(), fp);
|
|
fclose(fp);
|
|
}
|
|
|
|
fp = fopen(pair.second->m_meta.path.UTF8String, "wb");
|
|
if (fp)
|
|
{
|
|
fwrite(&*pair.second->m_metaData, 1, sizeof(*pair.second->m_metaData), fp);
|
|
fclose(fp);
|
|
}
|
|
}
|
|
}];
|
|
|
|
[self resetIterators];
|
|
return true;
|
|
}
|
|
|
|
- (void)update
|
|
{
|
|
NSFileCoordinator* coord = [[NSFileCoordinator alloc] initWithFilePresenter:self];
|
|
if (!coord)
|
|
return;
|
|
NSError* coordErr;
|
|
__block NSError* managerErr;
|
|
__block std::map<std::string, std::unique_ptr<AudioGroupCollection>>& theMap = m_audioGroupCollections;
|
|
__block AudioGroupFilePresenter* presenter = self;
|
|
[coord coordinateReadingItemAtURL:m_groupURL options:NSFileCoordinatorReadingResolvesSymbolicLink error:&coordErr
|
|
byAccessor:^(NSURL* newUrl)
|
|
{
|
|
NSFileManager* fman = [NSFileManager defaultManager];
|
|
NSArray<NSURL*>* contents =
|
|
[fman contentsOfDirectoryAtURL:newUrl
|
|
includingPropertiesForKeys:@[NSURLIsDirectoryKey]
|
|
options:NSDirectoryEnumerationSkipsSubdirectoryDescendants |
|
|
NSDirectoryEnumerationSkipsHiddenFiles
|
|
error:&managerErr];
|
|
if (!contents)
|
|
return;
|
|
|
|
for (NSURL* path in contents)
|
|
{
|
|
NSNumber* isDir;
|
|
[path getResourceValue:&isDir forKey:NSURLIsDirectoryKey error:nil];
|
|
|
|
if (isDir.boolValue)
|
|
{
|
|
auto search = theMap.find(path.lastPathComponent.UTF8String);
|
|
if (search == theMap.end())
|
|
{
|
|
search = theMap.emplace(path.lastPathComponent.UTF8String, std::make_unique<AudioGroupCollection>(path)).first;
|
|
search->second->update(presenter);
|
|
}
|
|
}
|
|
}
|
|
}];
|
|
|
|
[self resetIterators];
|
|
}
|
|
|
|
- (void)resetIterators
|
|
{
|
|
if ([(NSObject*)m_audioGroupClient isKindOfClass:[AppDelegate class]])
|
|
{
|
|
std::string search;
|
|
if (m_searchStr)
|
|
search = m_searchStr.UTF8String;
|
|
|
|
m_sfxTableData.clear();
|
|
m_sampleTableData.clear();
|
|
m_filterAudioGroupCollections.clear();
|
|
m_filterAudioGroupCollections.reserve(m_audioGroupCollections.size());
|
|
for (auto it = m_audioGroupCollections.begin() ; it != m_audioGroupCollections.end() ; ++it)
|
|
{
|
|
it->second->addSFX(m_sfxTableData);
|
|
it->second->addSamples(m_sampleTableData);
|
|
if (it->second->doSearch(search) || !m_searchStr || StrToLower(it->first).find(search) != std::string::npos)
|
|
m_filterAudioGroupCollections.push_back(it);
|
|
}
|
|
[m_lastOutlineView reloadItem:nil reloadChildren:YES];
|
|
[(AppDelegate*)m_audioGroupClient reloadTables];
|
|
}
|
|
else
|
|
{
|
|
m_sfxTableData.clear();
|
|
m_sampleTableData.clear();
|
|
m_filterAudioGroupCollections.clear();
|
|
m_filterAudioGroupCollections.reserve(m_audioGroupCollections.size());
|
|
for (auto it = m_audioGroupCollections.begin() ; it != m_audioGroupCollections.end() ; ++it)
|
|
{
|
|
it->second->addSFX(m_sfxTableData);
|
|
it->second->addSamples(m_sampleTableData);
|
|
if (it->second->doActiveFilter())
|
|
m_filterAudioGroupCollections.push_back(it);
|
|
}
|
|
[((AmuseAudioUnit*)m_audioGroupClient)->m_viewController->m_groupBrowser loadColumnZero];
|
|
}
|
|
}
|
|
|
|
- (void)setSearchFilter:(NSString*)str
|
|
{
|
|
m_searchStr = [str lowercaseString];
|
|
[self resetIterators];
|
|
}
|
|
|
|
- (void)removeSelectedItem
|
|
{
|
|
id item = [m_lastOutlineView itemAtRow:m_lastOutlineView.selectedRow];
|
|
if ([item isKindOfClass:[AudioGroupCollectionToken class]])
|
|
{
|
|
AudioGroupCollection& collection = *((AudioGroupCollectionToken*)item)->m_collection;
|
|
NSURL* collectionURL = collection.m_url;
|
|
NSString* lastComp = collectionURL.lastPathComponent;
|
|
m_audioGroupCollections.erase(lastComp.UTF8String);
|
|
[self resetIterators];
|
|
[[NSFileManager defaultManager] removeItemAtURL:collectionURL error:nil];
|
|
if (m_audioGroupCollections.empty())
|
|
[(AppDelegate*)NSApp.delegate outlineView:(DataOutlineView*)m_lastOutlineView selectionChanged:nil];
|
|
}
|
|
}
|
|
|
|
- (id)initWithAudioGroupClient:(id<AudioGroupClient>)client
|
|
{
|
|
m_audioGroupClient = client;
|
|
m_groupURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.com.axiodl.Amuse.AudioGroups"];
|
|
if (!m_groupURL)
|
|
return nil;
|
|
[NSFileCoordinator addFilePresenter:self];
|
|
[self update];
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
[NSFileCoordinator removeFilePresenter:self];
|
|
}
|
|
|
|
@end
|