mgrLM // Swiftify everything except Cpp-related stuff.

- This is not a complete swiftification since all ObjCpp-related parts are not swiftifiable.
- Rename invalid folder path target in lieu of removal.
- Let dataFolderPath() always ensure trailing slash.
- Also added process of verifying folder write access.
- Also simplify chkUserLMFilesExist(InputMode).
- Also enveloped LMConsolidator into an ObjC command in order to swiftify and refactor writeUserPhrase().
This commit is contained in:
ShikiSuen 2022-04-09 00:30:11 +08:00
parent 29a8a4683f
commit 3bed1dfecd
5 changed files with 280 additions and 264 deletions

View File

@ -85,7 +85,9 @@ import Cocoa
// MARK: - Open a phrase data file. // MARK: - Open a phrase data file.
static func openPhraseFile(userFileAt path: String) { static func openPhraseFile(userFileAt path: String) {
func checkIfUserFilesExist() -> Bool { func checkIfUserFilesExist() -> Bool {
if !mgrLangModel.checkIfUserLanguageModelFilesExist() { if !mgrLangModel.chkUserLMFilesExist(InputMode.imeModeCHS)
|| !mgrLangModel.chkUserLMFilesExist(InputMode.imeModeCHT)
{
let content = String( let content = String(
format: NSLocalizedString( format: NSLocalizedString(
"Please check the permission at \"%@\".", comment: ""), "Please check the permission at \"%@\".", comment: ""),

View File

@ -35,29 +35,15 @@ NS_ASSUME_NONNULL_BEGIN
+ (void)loadUserPhrases; + (void)loadUserPhrases;
+ (void)loadUserAssociatedPhrases; + (void)loadUserAssociatedPhrases;
+ (void)loadUserPhraseReplacement; + (void)loadUserPhraseReplacement;
+ (BOOL)checkIfUserLanguageModelFilesExist;
+ (BOOL)checkIfUserDataFolderExists;
+ (BOOL)checkIfSpecifiedUserDataFolderValid:(NSString *)folderPath;
+ (NSString *)dataFolderPath:(bool)isDefaultFolder NS_SWIFT_NAME(dataFolderPath(isDefaultFolder:));
+ (BOOL)checkIfUserPhraseExist:(NSString *)userPhrase + (BOOL)checkIfUserPhraseExist:(NSString *)userPhrase
inputMode:(InputMode)mode inputMode:(InputMode)mode
key:(NSString *)key NS_SWIFT_NAME(checkIfUserPhraseExist(userPhrase:mode:key:)); key:(NSString *)key NS_SWIFT_NAME(checkIfUserPhraseExist(userPhrase:mode:key:));
+ (BOOL)writeUserPhrase:(NSString *)userPhrase + (void)consolidateGivenFile:(NSString *)path shouldCheckPragma:(BOOL)shouldCheckPragma;
inputMode:(InputMode)mode
areWeDuplicating:(BOOL)areWeDuplicating
areWeDeleting:(BOOL)areWeDeleting;
+ (void)setPhraseReplacementEnabled:(BOOL)phraseReplacementEnabled; + (void)setPhraseReplacementEnabled:(BOOL)phraseReplacementEnabled;
+ (void)setCNSEnabled:(BOOL)cnsEnabled; + (void)setCNSEnabled:(BOOL)cnsEnabled;
+ (void)setSymbolEnabled:(BOOL)symbolEnabled; + (void)setSymbolEnabled:(BOOL)symbolEnabled;
+ (NSString *)specifyBundleDataPath:(NSString *)filename;
+ (NSString *)userPhrasesDataPath:(InputMode)mode;
+ (NSString *)userSymbolDataPath:(InputMode)mode;
+ (NSString *)userAssociatedPhrasesDataPath:(InputMode)mode;
+ (NSString *)excludedPhrasesDataPath:(InputMode)mode;
+ (NSString *)phraseReplacementDataPath:(InputMode)mode;
@end @end
/// The following methods are merely for testing. /// The following methods are merely for testing.

View File

@ -37,28 +37,16 @@ static vChewing::LMInstantiator gLangModelCHS;
static vChewing::UserOverrideModel gUserOverrideModelCHT(kUserOverrideModelCapacity, kObservedOverrideHalflife); static vChewing::UserOverrideModel gUserOverrideModelCHT(kUserOverrideModelCapacity, kObservedOverrideHalflife);
static vChewing::UserOverrideModel gUserOverrideModelCHS(kUserOverrideModelCapacity, kObservedOverrideHalflife); static vChewing::UserOverrideModel gUserOverrideModelCHS(kUserOverrideModelCapacity, kObservedOverrideHalflife);
static NSString *const kUserDataTemplateName = @"template-data";
static NSString *const kUserAssDataTemplateName = @"template-data";
static NSString *const kExcludedPhrasesvChewingTemplateName = @"template-exclude-phrases";
static NSString *const kPhraseReplacementTemplateName = @"template-phrases-replacement";
static NSString *const kUserSymbolDataTemplateName = @"template-user-symbol-data";
static NSString *const kTemplateExtension = @".txt";
@implementation mgrLangModel @implementation mgrLangModel
// 這個函數無法遷移至 Swift
static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing::LMInstantiator &lm) static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing::LMInstantiator &lm)
{ {
Class cls = NSClassFromString(@"ctlInputMethod"); NSString *dataPath = [mgrLangModel getBundleDataPath:filenameWithoutExtension];
NSString *dataPath = [[NSBundle bundleForClass:cls] pathForResource:filenameWithoutExtension ofType:@"txt"];
lm.loadLanguageModel([dataPath UTF8String]); lm.loadLanguageModel([dataPath UTF8String]);
} }
+ (NSString *)specifyBundleDataPath:(NSString *)filenameWithoutExtension; // 這個函數無法遷移至 Swift
{
Class cls = NSClassFromString(@"ctlInputMethod");
return [[NSBundle bundleForClass:cls] pathForResource:filenameWithoutExtension ofType:@"txt"];
}
+ (void)loadDataModels + (void)loadDataModels
{ {
if (!gLangModelCHT.isDataModelLoaded()) if (!gLangModelCHT.isDataModelLoaded())
@ -67,15 +55,15 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing
} }
if (!gLangModelCHT.isMiscDataLoaded()) if (!gLangModelCHT.isMiscDataLoaded())
{ {
gLangModelCHT.loadMiscData([[self specifyBundleDataPath:@"data-zhuyinwen"] UTF8String]); gLangModelCHT.loadMiscData([[self getBundleDataPath:@"data-zhuyinwen"] UTF8String]);
} }
if (!gLangModelCHT.isSymbolDataLoaded()) if (!gLangModelCHT.isSymbolDataLoaded())
{ {
gLangModelCHT.loadSymbolData([[self specifyBundleDataPath:@"data-symbols"] UTF8String]); gLangModelCHT.loadSymbolData([[self getBundleDataPath:@"data-symbols"] UTF8String]);
} }
if (!gLangModelCHT.isCNSDataLoaded()) if (!gLangModelCHT.isCNSDataLoaded())
{ {
gLangModelCHT.loadCNSData([[self specifyBundleDataPath:@"char-kanji-cns"] UTF8String]); gLangModelCHT.loadCNSData([[self getBundleDataPath:@"char-kanji-cns"] UTF8String]);
} }
// ----------------- // -----------------
if (!gLangModelCHS.isDataModelLoaded()) if (!gLangModelCHS.isDataModelLoaded())
@ -84,18 +72,19 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing
} }
if (!gLangModelCHS.isMiscDataLoaded()) if (!gLangModelCHS.isMiscDataLoaded())
{ {
gLangModelCHS.loadMiscData([[self specifyBundleDataPath:@"data-zhuyinwen"] UTF8String]); gLangModelCHS.loadMiscData([[self getBundleDataPath:@"data-zhuyinwen"] UTF8String]);
} }
if (!gLangModelCHS.isSymbolDataLoaded()) if (!gLangModelCHS.isSymbolDataLoaded())
{ {
gLangModelCHS.loadSymbolData([[self specifyBundleDataPath:@"data-symbols"] UTF8String]); gLangModelCHS.loadSymbolData([[self getBundleDataPath:@"data-symbols"] UTF8String]);
} }
if (!gLangModelCHS.isCNSDataLoaded()) if (!gLangModelCHS.isCNSDataLoaded())
{ {
gLangModelCHS.loadCNSData([[self specifyBundleDataPath:@"char-kanji-cns"] UTF8String]); gLangModelCHS.loadCNSData([[self getBundleDataPath:@"char-kanji-cns"] UTF8String]);
} }
} }
// 這個函數無法遷移至 Swift
+ (void)loadDataModel:(InputMode)mode + (void)loadDataModel:(InputMode)mode
{ {
if ([mode isEqualToString:imeModeCHT]) if ([mode isEqualToString:imeModeCHT])
@ -106,15 +95,15 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing
} }
if (!gLangModelCHT.isMiscDataLoaded()) if (!gLangModelCHT.isMiscDataLoaded())
{ {
gLangModelCHT.loadMiscData([[self specifyBundleDataPath:@"data-zhuyinwen"] UTF8String]); gLangModelCHT.loadMiscData([[self getBundleDataPath:@"data-zhuyinwen"] UTF8String]);
} }
if (!gLangModelCHT.isSymbolDataLoaded()) if (!gLangModelCHT.isSymbolDataLoaded())
{ {
gLangModelCHT.loadSymbolData([[self specifyBundleDataPath:@"data-symbols"] UTF8String]); gLangModelCHT.loadSymbolData([[self getBundleDataPath:@"data-symbols"] UTF8String]);
} }
if (!gLangModelCHT.isCNSDataLoaded()) if (!gLangModelCHT.isCNSDataLoaded())
{ {
gLangModelCHT.loadCNSData([[self specifyBundleDataPath:@"char-kanji-cns"] UTF8String]); gLangModelCHT.loadCNSData([[self getBundleDataPath:@"char-kanji-cns"] UTF8String]);
} }
} }
@ -126,19 +115,20 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing
} }
if (!gLangModelCHS.isMiscDataLoaded()) if (!gLangModelCHS.isMiscDataLoaded())
{ {
gLangModelCHS.loadMiscData([[self specifyBundleDataPath:@"data-zhuyinwen"] UTF8String]); gLangModelCHS.loadMiscData([[self getBundleDataPath:@"data-zhuyinwen"] UTF8String]);
} }
if (!gLangModelCHS.isSymbolDataLoaded()) if (!gLangModelCHS.isSymbolDataLoaded())
{ {
gLangModelCHS.loadSymbolData([[self specifyBundleDataPath:@"data-symbols"] UTF8String]); gLangModelCHS.loadSymbolData([[self getBundleDataPath:@"data-symbols"] UTF8String]);
} }
if (!gLangModelCHS.isCNSDataLoaded()) if (!gLangModelCHS.isCNSDataLoaded())
{ {
gLangModelCHS.loadCNSData([[self specifyBundleDataPath:@"char-kanji-cns"] UTF8String]); gLangModelCHS.loadCNSData([[self getBundleDataPath:@"char-kanji-cns"] UTF8String]);
} }
} }
} }
// 這個函數無法遷移至 Swift
+ (void)loadUserPhrases + (void)loadUserPhrases
{ {
gLangModelCHT.loadUserPhrases([[self userPhrasesDataPath:imeModeCHT] UTF8String], gLangModelCHT.loadUserPhrases([[self userPhrasesDataPath:imeModeCHT] UTF8String],
@ -149,136 +139,21 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing
gLangModelCHS.loadUserSymbolData([[self userSymbolDataPath:imeModeCHS] UTF8String]); gLangModelCHS.loadUserSymbolData([[self userSymbolDataPath:imeModeCHS] UTF8String]);
} }
// 這個函數無法遷移至 Swift
+ (void)loadUserAssociatedPhrases + (void)loadUserAssociatedPhrases
{ {
gLangModelCHT.loadUserAssociatedPhrases([[self userAssociatedPhrasesDataPath:imeModeCHT] UTF8String]); gLangModelCHT.loadUserAssociatedPhrases([[self userAssociatedPhrasesDataPath:imeModeCHT] UTF8String]);
gLangModelCHS.loadUserAssociatedPhrases([[self userAssociatedPhrasesDataPath:imeModeCHS] UTF8String]); gLangModelCHS.loadUserAssociatedPhrases([[self userAssociatedPhrasesDataPath:imeModeCHS] UTF8String]);
} }
// 這個函數無法遷移至 Swift
+ (void)loadUserPhraseReplacement + (void)loadUserPhraseReplacement
{ {
gLangModelCHT.loadPhraseReplacementMap([[self phraseReplacementDataPath:imeModeCHT] UTF8String]); gLangModelCHT.loadPhraseReplacementMap([[self phraseReplacementDataPath:imeModeCHT] UTF8String]);
gLangModelCHS.loadPhraseReplacementMap([[self phraseReplacementDataPath:imeModeCHS] UTF8String]); gLangModelCHS.loadPhraseReplacementMap([[self phraseReplacementDataPath:imeModeCHS] UTF8String]);
} }
+ (BOOL)checkIfUserDataFolderExists // 這個函數無法遷移至 Swift
{
NSString *folderPath = [self dataFolderPath:false];
BOOL isFolder = NO;
BOOL folderExist = [[NSFileManager defaultManager] fileExistsAtPath:folderPath isDirectory:&isFolder];
if (folderExist && !isFolder)
{
NSError *error = nil;
[[NSFileManager defaultManager] removeItemAtPath:folderPath error:&error];
if (error)
{
NSLog(@"Failed to remove folder %@", error);
return NO;
}
folderExist = NO;
}
if (!folderExist)
{
NSError *error = nil;
[[NSFileManager defaultManager] createDirectoryAtPath:folderPath
withIntermediateDirectories:YES
attributes:nil
error:&error];
if (error)
{
NSLog(@"Failed to create folder %@", error);
return NO;
}
}
return YES;
}
+ (BOOL)checkIfSpecifiedUserDataFolderValid:(NSString *)folderPath
{
BOOL isFolder = NO;
BOOL folderExist = [[NSFileManager defaultManager] fileExistsAtPath:folderPath isDirectory:&isFolder];
if ((folderExist && !isFolder) || (!folderExist))
{
return NO;
}
return YES;
}
+ (BOOL)ensureFileExists:(NSString *)filePath
populateWithTemplate:(NSString *)templateBasename
extension:(NSString *)ext
{
if (![[NSFileManager defaultManager] fileExistsAtPath:filePath])
{
NSURL *templateURL = [[NSBundle mainBundle] URLForResource:templateBasename withExtension:ext];
NSData *templateData;
if (templateURL)
{
templateData = [NSData dataWithContentsOfURL:templateURL];
}
else
{
templateData = [@"" dataUsingEncoding:NSUTF8StringEncoding];
}
BOOL result = [templateData writeToFile:filePath atomically:YES];
if (!result)
{
NSLog(@"Failed to write file");
return NO;
}
}
return YES;
}
+ (BOOL)checkIfUserLanguageModelFilesExist
{
if (![self checkIfUserDataFolderExists])
return NO;
if (![self ensureFileExists:[self userPhrasesDataPath:imeModeCHS]
populateWithTemplate:kUserDataTemplateName
extension:kTemplateExtension])
return NO;
if (![self ensureFileExists:[self userPhrasesDataPath:imeModeCHT]
populateWithTemplate:kUserDataTemplateName
extension:kTemplateExtension])
return NO;
if (![self ensureFileExists:[self userAssociatedPhrasesDataPath:imeModeCHS]
populateWithTemplate:kUserAssDataTemplateName
extension:kTemplateExtension])
return NO;
if (![self ensureFileExists:[self userAssociatedPhrasesDataPath:imeModeCHT]
populateWithTemplate:kUserAssDataTemplateName
extension:kTemplateExtension])
return NO;
if (![self ensureFileExists:[self excludedPhrasesDataPath:imeModeCHS]
populateWithTemplate:kExcludedPhrasesvChewingTemplateName
extension:kTemplateExtension])
return NO;
if (![self ensureFileExists:[self excludedPhrasesDataPath:imeModeCHT]
populateWithTemplate:kExcludedPhrasesvChewingTemplateName
extension:kTemplateExtension])
return NO;
if (![self ensureFileExists:[self phraseReplacementDataPath:imeModeCHS]
populateWithTemplate:kPhraseReplacementTemplateName
extension:kTemplateExtension])
return NO;
if (![self ensureFileExists:[self phraseReplacementDataPath:imeModeCHT]
populateWithTemplate:kPhraseReplacementTemplateName
extension:kTemplateExtension])
return NO;
if (![self ensureFileExists:[self userSymbolDataPath:imeModeCHT]
populateWithTemplate:kUserSymbolDataTemplateName
extension:kTemplateExtension])
return NO;
if (![self ensureFileExists:[self userSymbolDataPath:imeModeCHS]
populateWithTemplate:kUserSymbolDataTemplateName
extension:kTemplateExtension])
return NO;
return YES;
}
+ (BOOL)checkIfUserPhraseExist:(NSString *)userPhrase + (BOOL)checkIfUserPhraseExist:(NSString *)userPhrase
inputMode:(InputMode)mode inputMode:(InputMode)mode
key:(NSString *)key NS_SWIFT_NAME(checkIfUserPhraseExist(userPhrase:mode:key:)) key:(NSString *)key NS_SWIFT_NAME(checkIfUserPhraseExist(userPhrase:mode:key:))
@ -297,144 +172,51 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing
return NO; return NO;
} }
+ (BOOL)writeUserPhrase:(NSString *)userPhrase // 這個函數無法遷移至 Swift
inputMode:(InputMode)mode + (void)consolidateGivenFile:(NSString *)path shouldCheckPragma:(BOOL)shouldCheckPragma
areWeDuplicating:(BOOL)areWeDuplicating
areWeDeleting:(BOOL)areWeDeleting
{ {
if (![self checkIfUserLanguageModelFilesExist]) vChewing::LMConsolidator::ConsolidateContent([path UTF8String], shouldCheckPragma);
{
return NO;
}
// BOOL addLineBreakAtFront = NO;
NSString *path = areWeDeleting ? [self excludedPhrasesDataPath:mode] : [self userPhrasesDataPath:mode];
NSMutableString *currentMarkedPhrase = [NSMutableString string];
// if (addLineBreakAtFront) {
// [currentMarkedPhrase appendString:@"\n"];
// }
[currentMarkedPhrase appendString:userPhrase];
if (areWeDuplicating && !areWeDeleting)
{
// Do not use ASCII characters to comment here.
// Otherwise, it will be scrambled by cnvHYPYtoBPMF module shipped in the vChewing Phrase Editor.
[currentMarkedPhrase appendString:@"\t#𝙾𝚟𝚎𝚛𝚛𝚒𝚍𝚎"];
}
[currentMarkedPhrase appendString:@"\n"];
NSFileHandle *writeFile = [NSFileHandle fileHandleForUpdatingAtPath:path];
if (!writeFile)
{
return NO;
}
[writeFile seekToEndOfFile];
NSData *data = [currentMarkedPhrase dataUsingEncoding:NSUTF8StringEncoding];
[writeFile writeData:data];
[writeFile closeFile];
// We enforce the format consolidation here, since the pragma header will let the UserPhraseLM bypasses the
// consolidating process on load.
vChewing::LMConsolidator::ConsolidateContent([path UTF8String], false);
// We use FSEventStream to monitor the change of the user phrase folder,
// so we don't have to load data here unless FSEventStream is disabled by user.
if (!mgrPrefs.shouldAutoReloadUserDataFiles)
{
[self loadUserPhrases];
}
return YES;
}
+ (NSString *)dataFolderPath:(bool)isDefaultFolder
{
// 此處不能用「~」來取代當前使用者目錄名稱。不然的話,一旦輸入法被系統的沙箱干預的話,則反而會定位到沙箱目錄內。
NSString *appSupportPath = [NSFileManager.defaultManager URLsForDirectory:NSApplicationSupportDirectory
inDomains:NSUserDomainMask][0].path;
NSString *userDictPath = [appSupportPath stringByAppendingPathComponent:@"vChewing"].stringByExpandingTildeInPath;
if (mgrPrefs.userDataFolderSpecified.stringByExpandingTildeInPath == userDictPath || isDefaultFolder)
{
return userDictPath;
}
if ([mgrPrefs ifSpecifiedUserDataPathExistsInPlist])
{
if ([self checkIfSpecifiedUserDataFolderValid:mgrPrefs.userDataFolderSpecified.stringByExpandingTildeInPath])
{
return mgrPrefs.userDataFolderSpecified.stringByExpandingTildeInPath;
}
else
{
[NSUserDefaults.standardUserDefaults removeObjectForKey:@"UserDataFolderSpecified"];
}
}
return userDictPath;
}
+ (NSString *)userPhrasesDataPath:(InputMode)mode;
{
NSString *fileName = [mode isEqualToString:imeModeCHT] ? @"userdata-cht.txt" : @"userdata-chs.txt";
return [[self dataFolderPath:false] stringByAppendingPathComponent:fileName];
}
+ (NSString *)userSymbolDataPath:(InputMode)mode;
{
NSString *fileName =
[mode isEqualToString:imeModeCHT] ? @"usersymbolphrases-cht.txt" : @"usersymbolphrases-chs.txt";
return [[self dataFolderPath:false] stringByAppendingPathComponent:fileName];
}
+ (NSString *)userAssociatedPhrasesDataPath:(InputMode)mode;
{
NSString *fileName =
[mode isEqualToString:imeModeCHT] ? @"associatedPhrases-cht.txt" : @"associatedPhrases-chs.txt";
return [[self dataFolderPath:false] stringByAppendingPathComponent:fileName];
}
+ (NSString *)excludedPhrasesDataPath:(InputMode)mode;
{
NSString *fileName = [mode isEqualToString:imeModeCHT] ? @"exclude-phrases-cht.txt" : @"exclude-phrases-chs.txt";
return [[self dataFolderPath:false] stringByAppendingPathComponent:fileName];
}
+ (NSString *)phraseReplacementDataPath:(InputMode)mode;
{
NSString *fileName =
[mode isEqualToString:imeModeCHT] ? @"phrases-replacement-cht.txt" : @"phrases-replacement-chs.txt";
return [[self dataFolderPath:false] stringByAppendingPathComponent:fileName];
} }
// 這個函數無法遷移至 Swift
+ (vChewing::LMInstantiator *)lmCHT + (vChewing::LMInstantiator *)lmCHT
{ {
return &gLangModelCHT; return &gLangModelCHT;
} }
// 這個函數無法遷移至 Swift
+ (vChewing::LMInstantiator *)lmCHS + (vChewing::LMInstantiator *)lmCHS
{ {
return &gLangModelCHS; return &gLangModelCHS;
} }
// 這個函數無法遷移至 Swift
+ (vChewing::UserOverrideModel *)userOverrideModelCHT + (vChewing::UserOverrideModel *)userOverrideModelCHT
{ {
return &gUserOverrideModelCHT; return &gUserOverrideModelCHT;
} }
// 這個函數無法遷移至 Swift
+ (vChewing::UserOverrideModel *)userOverrideModelCHS + (vChewing::UserOverrideModel *)userOverrideModelCHS
{ {
return &gUserOverrideModelCHS; return &gUserOverrideModelCHS;
} }
// 這個函數無法遷移至 Swift
+ (void)setPhraseReplacementEnabled:(BOOL)phraseReplacementEnabled + (void)setPhraseReplacementEnabled:(BOOL)phraseReplacementEnabled
{ {
gLangModelCHT.setPhraseReplacementEnabled(phraseReplacementEnabled); gLangModelCHT.setPhraseReplacementEnabled(phraseReplacementEnabled);
gLangModelCHS.setPhraseReplacementEnabled(phraseReplacementEnabled); gLangModelCHS.setPhraseReplacementEnabled(phraseReplacementEnabled);
} }
// 這個函數無法遷移至 Swift
+ (void)setCNSEnabled:(BOOL)cnsEnabled + (void)setCNSEnabled:(BOOL)cnsEnabled
{ {
gLangModelCHT.setCNSEnabled(cnsEnabled); gLangModelCHT.setCNSEnabled(cnsEnabled);
gLangModelCHS.setCNSEnabled(cnsEnabled); gLangModelCHS.setCNSEnabled(cnsEnabled);
} }
// 這個函數無法遷移至 Swift
+ (void)setSymbolEnabled:(BOOL)symbolEnabled + (void)setSymbolEnabled:(BOOL)symbolEnabled
{ {
gLangModelCHT.setSymbolEnabled(symbolEnabled); gLangModelCHT.setSymbolEnabled(symbolEnabled);

View File

@ -0,0 +1,242 @@
// Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License).
/*
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
1. The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
2. No trademark license is granted to use the trade names, trademarks, service
marks, or product names of Contributor, except as required to fulfill notice
requirements above.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import Cocoa
@objc extension mgrLangModel {
// MARK: -
static func getBundleDataPath(_ filenameSansExt: String) -> String {
return Bundle.main.path(forResource: filenameSansExt, ofType: "txt")!
}
// MARK: - 使
// Swift appendingPathComponent URL .path
static func userPhrasesDataPath(_ mode: InputMode) -> String {
let fileName = (mode == InputMode.imeModeCHT) ? "userdata-cht.txt" : "userdata-chs.txt"
return URL(fileURLWithPath: self.dataFolderPath(isDefaultFolder: false)).appendingPathComponent(fileName).path
}
static func userSymbolDataPath(_ mode: InputMode) -> String {
let fileName = (mode == InputMode.imeModeCHT) ? "usersymbolphrases-cht.txt" : "usersymbolphrases-chs.txt"
return URL(fileURLWithPath: self.dataFolderPath(isDefaultFolder: false)).appendingPathComponent(fileName).path
}
static func userAssociatedPhrasesDataPath(_ mode: InputMode) -> String {
let fileName = (mode == InputMode.imeModeCHT) ? "associatedPhrases-cht.txt" : "associatedPhrases-chs.txt"
return URL(fileURLWithPath: self.dataFolderPath(isDefaultFolder: false)).appendingPathComponent(fileName).path
}
static func excludedPhrasesDataPath(_ mode: InputMode) -> String {
let fileName = (mode == InputMode.imeModeCHT) ? "exclude-phrases-cht.txt" : "exclude-phrases-chs.txt"
return URL(fileURLWithPath: self.dataFolderPath(isDefaultFolder: false)).appendingPathComponent(fileName).path
}
static func phraseReplacementDataPath(_ mode: InputMode) -> String {
let fileName = (mode == InputMode.imeModeCHT) ? "phrases-replacement-cht.txt" : "phrases-replacement-chs.txt"
return URL(fileURLWithPath: self.dataFolderPath(isDefaultFolder: false)).appendingPathComponent(fileName).path
}
// MARK: - 使
static func ensureFileExists(
_ filePath: String, populateWithTemplate templateBasename: String = "1145141919810",
extension ext: String = "txt"
) -> Bool {
if !FileManager.default.fileExists(atPath: filePath) {
let templateURL = Bundle.main.url(forResource: templateBasename, withExtension: ext)
var templateData = Data("".utf8)
if templateBasename != "" {
do {
try templateData = Data(contentsOf: templateURL ?? URL(fileURLWithPath: ""))
} catch {
templateData = Data("".utf8)
}
do {
try templateData.write(to: URL(fileURLWithPath: filePath))
} catch {
IME.prtDebugIntel("Failed to write file")
return false
}
}
}
return true
}
static func chkUserLMFilesExist(_ mode: InputMode) -> Bool {
if !self.checkIfUserDataFolderExists() {
return false
}
if !ensureFileExists(userPhrasesDataPath(mode))
|| !ensureFileExists(userAssociatedPhrasesDataPath(mode))
|| !ensureFileExists(excludedPhrasesDataPath(mode))
|| !ensureFileExists(phraseReplacementDataPath(mode))
|| !ensureFileExists(userSymbolDataPath(mode))
{
return false
}
return true
}
// MARK: - 使
//
static func checkIfSpecifiedUserDataFolderValid(_ folderPath: String?) -> Bool {
var isFolder = ObjCBool(false)
let folderExist = FileManager.default.fileExists(atPath: folderPath ?? "", isDirectory: &isFolder)
// The above "&" mutates the "isFolder" value to the real one received by the "folderExist".
//
//
var folderPath = folderPath // Convert the incoming constant to a variable.
if isFolder.boolValue {
folderPath?.ensureTrailingSlash()
}
let isFolderWritable = FileManager.default.isWritableFile(atPath: folderPath ?? "")
if ((folderExist && !isFolder.boolValue) || !folderExist) || !isFolderWritable {
return false
}
return true
}
//
//
static func checkIfUserDataFolderExists() -> Bool {
let folderPath = mgrLangModel.dataFolderPath(isDefaultFolder: false)
var isFolder = ObjCBool(false)
var folderExist = FileManager.default.fileExists(atPath: folderPath, isDirectory: &isFolder)
// The above "&" mutates the "isFolder" value to the real one received by the "folderExist".
//
//
//
if folderExist && !isFolder.boolValue {
do {
if self.dataFolderPath(isDefaultFolder: false)
== self.dataFolderPath(isDefaultFolder: true)
{
let formatter = DateFormatter.init()
formatter.dateFormat = "YYYYMMDD-HHMM'Hrs'-ss's'"
let dirAlternative = folderPath + formatter.string(from: Date())
try FileManager.default.moveItem(atPath: folderPath, toPath: dirAlternative)
} else {
throw folderPath
}
} catch {
print("Failed to make path available at: \(error)")
return false
}
folderExist = false
}
if !folderExist {
do {
try FileManager.default.createDirectory(
atPath: folderPath,
withIntermediateDirectories: true,
attributes: nil)
} catch {
print("Failed to create folder: \(error)")
return false
}
}
return true
}
// MARK: - 使 mgrPrefs
// mgrPrefs
static func dataFolderPath(isDefaultFolder: Bool) -> String {
let appSupportPath = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0].path
var userDictPathSpecified = (mgrPrefs.userDataFolderSpecified as NSString).expandingTildeInPath
var userDictPathDefault =
(URL(fileURLWithPath: appSupportPath).appendingPathComponent("vChewing").path as NSString)
.expandingTildeInPath
userDictPathDefault.ensureTrailingSlash()
userDictPathSpecified.ensureTrailingSlash()
if (userDictPathSpecified == userDictPathDefault)
|| isDefaultFolder
{
return userDictPathDefault
}
if mgrPrefs.ifSpecifiedUserDataPathExistsInPlist() {
if mgrLangModel.checkIfSpecifiedUserDataFolderValid(userDictPathSpecified) {
return userDictPathSpecified
} else {
UserDefaults.standard.removeObject(forKey: "UserDataFolderSpecified")
}
}
return userDictPathDefault
}
// MARK: - 使
static func writeUserPhrase(
_ userPhrase: String?, inputMode mode: InputMode, areWeDuplicating: Bool, areWeDeleting: Bool
) -> Bool {
if var currentMarkedPhrase: String = userPhrase {
if !self.chkUserLMFilesExist(InputMode.imeModeCHS)
|| !self.chkUserLMFilesExist(InputMode.imeModeCHT)
{
return false
}
let path = areWeDeleting ? self.excludedPhrasesDataPath(mode) : self.userPhrasesDataPath(mode)
if areWeDuplicating && !areWeDeleting {
// Do not use ASCII characters to comment here.
// Otherwise, it will be scrambled by cnvHYPYtoBPMF
// module shipped in the vChewing Phrase Editor.
currentMarkedPhrase += "\t#𝙾𝚟𝚎𝚛𝚛𝚒𝚍𝚎"
}
currentMarkedPhrase += "\n"
if let writeFile = FileHandle(forUpdatingAtPath: path),
let data = currentMarkedPhrase.data(using: .utf8)
{
writeFile.seekToEndOfFile()
writeFile.write(data)
writeFile.closeFile()
} else {
return false
}
// We enforce the format consolidation here, since the pragma header
// will let the UserPhraseLM bypasses the consolidating process on load.
self.consolidate(givenFile: path, shouldCheckPragma: false)
// We use FSEventStream to monitor possible changes of the user phrase folder, hence the
// lack of the needs of manually load data here unless FSEventStream is disabled by user.
if !mgrPrefs.shouldAutoReloadUserDataFiles {
self.loadUserPhrases()
}
return true
}
return false
}
}

View File

@ -50,6 +50,7 @@
5BA9FD4A27FEF3C9002DE248 /* PreferencesTabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD3D27FEF3C8002DE248 /* PreferencesTabViewController.swift */; }; 5BA9FD4A27FEF3C9002DE248 /* PreferencesTabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD3D27FEF3C8002DE248 /* PreferencesTabViewController.swift */; };
5BA9FD8B28006B41002DE248 /* VDKComboBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD8A28006B41002DE248 /* VDKComboBox.swift */; }; 5BA9FD8B28006B41002DE248 /* VDKComboBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD8A28006B41002DE248 /* VDKComboBox.swift */; };
5BAD0CD527D701F6003D127F /* vChewingKeyLayout.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 5B30F11227BA568800484E24 /* vChewingKeyLayout.bundle */; }; 5BAD0CD527D701F6003D127F /* vChewingKeyLayout.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 5B30F11227BA568800484E24 /* vChewingKeyLayout.bundle */; };
5BAEFAD028012565001F42C9 /* mgrLangModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BAEFACF28012565001F42C9 /* mgrLangModel.swift */; };
5BB802DA27FABA8300CF1C19 /* ctlInputMethod_Menu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BB802D927FABA8300CF1C19 /* ctlInputMethod_Menu.swift */; }; 5BB802DA27FABA8300CF1C19 /* ctlInputMethod_Menu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BB802D927FABA8300CF1C19 /* ctlInputMethod_Menu.swift */; };
5BBBB75F27AED54C0023B93A /* Beep.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 5BBBB75D27AED54C0023B93A /* Beep.m4a */; }; 5BBBB75F27AED54C0023B93A /* Beep.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 5BBBB75D27AED54C0023B93A /* Beep.m4a */; };
5BBBB76027AED54C0023B93A /* Fart.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 5BBBB75E27AED54C0023B93A /* Fart.m4a */; }; 5BBBB76027AED54C0023B93A /* Fart.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 5BBBB75E27AED54C0023B93A /* Fart.m4a */; };
@ -227,6 +228,7 @@
5BA9FD3C27FEF3C8002DE248 /* Section.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Section.swift; sourceTree = "<group>"; }; 5BA9FD3C27FEF3C8002DE248 /* Section.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Section.swift; sourceTree = "<group>"; };
5BA9FD3D27FEF3C8002DE248 /* PreferencesTabViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesTabViewController.swift; sourceTree = "<group>"; }; 5BA9FD3D27FEF3C8002DE248 /* PreferencesTabViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesTabViewController.swift; sourceTree = "<group>"; };
5BA9FD8A28006B41002DE248 /* VDKComboBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VDKComboBox.swift; sourceTree = "<group>"; }; 5BA9FD8A28006B41002DE248 /* VDKComboBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VDKComboBox.swift; sourceTree = "<group>"; };
5BAEFACF28012565001F42C9 /* mgrLangModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = mgrLangModel.swift; sourceTree = "<group>"; };
5BB802D927FABA8300CF1C19 /* ctlInputMethod_Menu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ctlInputMethod_Menu.swift; sourceTree = "<group>"; }; 5BB802D927FABA8300CF1C19 /* ctlInputMethod_Menu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ctlInputMethod_Menu.swift; sourceTree = "<group>"; };
5BBBB75D27AED54C0023B93A /* Beep.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = Beep.m4a; sourceTree = "<group>"; }; 5BBBB75D27AED54C0023B93A /* Beep.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = Beep.m4a; sourceTree = "<group>"; };
5BBBB75E27AED54C0023B93A /* Fart.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = Fart.m4a; sourceTree = "<group>"; }; 5BBBB75E27AED54C0023B93A /* Fart.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = Fart.m4a; sourceTree = "<group>"; };
@ -478,6 +480,7 @@
D41355D6278D7409005E5CBD /* mgrLangModel.h */, D41355D6278D7409005E5CBD /* mgrLangModel.h */,
D495583A27A5C6C4006ADE1C /* mgrLangModel_Privates.h */, D495583A27A5C6C4006ADE1C /* mgrLangModel_Privates.h */,
D41355D7278D7409005E5CBD /* mgrLangModel.mm */, D41355D7278D7409005E5CBD /* mgrLangModel.mm */,
5BAEFACF28012565001F42C9 /* mgrLangModel.swift */,
5B62A32527AE758000A19448 /* SubLanguageModels */, 5B62A32527AE758000A19448 /* SubLanguageModels */,
); );
path = LangModelRelated; path = LangModelRelated;
@ -1086,6 +1089,7 @@
5B62A32927AE77D100A19448 /* FSEventStreamHelper.swift in Sources */, 5B62A32927AE77D100A19448 /* FSEventStreamHelper.swift in Sources */,
D47F7DD3278C1263002F9DD7 /* UserOverrideModel.cpp in Sources */, D47F7DD3278C1263002F9DD7 /* UserOverrideModel.cpp in Sources */,
5B62A33627AE795800A19448 /* mgrPrefs.swift in Sources */, 5B62A33627AE795800A19448 /* mgrPrefs.swift in Sources */,
5BAEFAD028012565001F42C9 /* mgrLangModel.swift in Sources */,
5B62A33827AE79CD00A19448 /* NSStringUtils.swift in Sources */, 5B62A33827AE79CD00A19448 /* NSStringUtils.swift in Sources */,
5BA9FD0F27FEDB6B002DE248 /* suiPrefPaneGeneral.swift in Sources */, 5BA9FD0F27FEDB6B002DE248 /* suiPrefPaneGeneral.swift in Sources */,
5BA9FD4927FEF3C9002DE248 /* Section.swift in Sources */, 5BA9FD4927FEF3C9002DE248 /* Section.swift in Sources */,