Merge branch 'master' into more-tolerant-userphraseslm
This commit is contained in:
commit
9bc3536630
|
@ -3,7 +3,7 @@ on: [push]
|
|||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
name: Build and Test
|
||||
runs-on: macOS-latest
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode.app/Contents/Developer
|
||||
|
@ -15,11 +15,23 @@ jobs:
|
|||
- name: Run McBopomofoLMLibTest
|
||||
run: make runTest
|
||||
working-directory: Source/Engine/build
|
||||
- name: Clean
|
||||
- name: Test McBopomofo App Bundle
|
||||
run: xcodebuild -scheme McBopomofo -configuration Debug test
|
||||
- name: Test CandidateUI
|
||||
run: swift test
|
||||
working-directory: Packages/CandidateUI
|
||||
- name: Test OpenCCBridge
|
||||
run: swift test
|
||||
working-directory: Packages/OpenCCBridge
|
||||
- name: Test VXHanConvert
|
||||
run: swift test
|
||||
working-directory: Packages/VXHanConvert
|
||||
- name: Clean McBopomofo
|
||||
run: xcodebuild -scheme McBopomofo -configuration Release clean
|
||||
- name: Clean
|
||||
- name: Clean McBopomofoInstaller
|
||||
run: xcodebuild -scheme McBopomofoInstaller -configuration Release clean
|
||||
- name: Build
|
||||
- name: Build McBopomofo
|
||||
run: xcodebuild -scheme McBopomofo -configuration Release build
|
||||
- name: Build
|
||||
- name: Build McBopomofoInstaller
|
||||
run: xcodebuild -scheme McBopomofoInstaller -configuration Release build
|
||||
|
||||
|
|
|
@ -23,6 +23,10 @@
|
|||
6A2E40F9253A6AA000D1AE1D /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6A2E40F5253A69DA00D1AE1D /* Images.xcassets */; };
|
||||
6A38BC1515FC117A00A8A51F /* data.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6A38BBF615FC117A00A8A51F /* data.txt */; };
|
||||
6A38BC2815FC158A00A8A51F /* InputMethodKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A38BC2715FC158A00A8A51F /* InputMethodKit.framework */; };
|
||||
6A6ED16B2797650A0012872E /* template-phrases-replacement.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6A6ED1632797650A0012872E /* template-phrases-replacement.txt */; };
|
||||
6A6ED16C2797650A0012872E /* template-data.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6A6ED1652797650A0012872E /* template-data.txt */; };
|
||||
6A6ED16D2797650A0012872E /* template-exclude-phrases-plain-bpmf.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6A6ED1672797650A0012872E /* template-exclude-phrases-plain-bpmf.txt */; };
|
||||
6A6ED16E2797650A0012872E /* template-exclude-phrases.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6A6ED1692797650A0012872E /* template-exclude-phrases.txt */; };
|
||||
6ACA41CD15FC1D7500935EF6 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A0D4EA615FC0D2D00ABF4B3 /* Cocoa.framework */; };
|
||||
6ACA41F915FC1D9000935EF6 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 6ACA41E915FC1D9000935EF6 /* AppDelegate.m */; };
|
||||
6ACA41FA15FC1D9000935EF6 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6ACA41EA15FC1D9000935EF6 /* InfoPlist.strings */; };
|
||||
|
@ -55,6 +59,8 @@
|
|||
D47F7DCE278BFB57002F9DD7 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DCD278BFB57002F9DD7 /* PreferencesWindowController.swift */; };
|
||||
D47F7DD0278C0897002F9DD7 /* NonModalAlertWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DCF278C0897002F9DD7 /* NonModalAlertWindowController.swift */; };
|
||||
D47F7DD3278C1263002F9DD7 /* UserOverrideModel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DD2278C1263002F9DD7 /* UserOverrideModel.cpp */; };
|
||||
D485D3B92796A8A000657FF3 /* PreferencesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D485D3B82796A8A000657FF3 /* PreferencesTests.swift */; };
|
||||
D485D3C02796CE3200657FF3 /* VersionUpdateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D485D3BF2796CE3200657FF3 /* VersionUpdateTests.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
|
@ -72,6 +78,13 @@
|
|||
remoteGlobalIDString = 6A0D4EA115FC0D2D00ABF4B3;
|
||||
remoteInfo = McBopomofo;
|
||||
};
|
||||
D485D3BA2796A8A000657FF3 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 6A0D4E9415FC0CFA00ABF4B3 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 6A0D4EA115FC0D2D00ABF4B3;
|
||||
remoteInfo = McBopomofo;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
|
@ -146,6 +159,14 @@
|
|||
6A2E40F5253A69DA00D1AE1D /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
|
||||
6A38BBF615FC117A00A8A51F /* data.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = data.txt; sourceTree = "<group>"; };
|
||||
6A38BC2715FC158A00A8A51F /* InputMethodKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = InputMethodKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.8.sdk/System/Library/Frameworks/InputMethodKit.framework; sourceTree = DEVELOPER_DIR; };
|
||||
6A6ED1642797650A0012872E /* Base */ = {isa = PBXFileReference; lastKnownFileType = text; name = Base; path = "Base.lproj/template-phrases-replacement.txt"; sourceTree = "<group>"; };
|
||||
6A6ED1662797650A0012872E /* Base */ = {isa = PBXFileReference; lastKnownFileType = text; name = Base; path = "Base.lproj/template-data.txt"; sourceTree = "<group>"; };
|
||||
6A6ED1682797650A0012872E /* Base */ = {isa = PBXFileReference; lastKnownFileType = text; name = Base; path = "Base.lproj/template-exclude-phrases-plain-bpmf.txt"; sourceTree = "<group>"; };
|
||||
6A6ED16A2797650A0012872E /* Base */ = {isa = PBXFileReference; lastKnownFileType = text; name = Base; path = "Base.lproj/template-exclude-phrases.txt"; sourceTree = "<group>"; };
|
||||
6A6ED16F279765100012872E /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text; name = "zh-Hant"; path = "zh-Hant.lproj/template-data.txt"; sourceTree = "<group>"; };
|
||||
6A6ED170279765140012872E /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text; name = "zh-Hant"; path = "zh-Hant.lproj/template-exclude-phrases-plain-bpmf.txt"; sourceTree = "<group>"; };
|
||||
6A6ED171279765170012872E /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text; name = "zh-Hant"; path = "zh-Hant.lproj/template-exclude-phrases.txt"; sourceTree = "<group>"; };
|
||||
6A6ED1722797651A0012872E /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text; name = "zh-Hant"; path = "zh-Hant.lproj/template-phrases-replacement.txt"; sourceTree = "<group>"; };
|
||||
6ACA41CB15FC1D7500935EF6 /* McBopomofoInstaller.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = McBopomofoInstaller.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
6ACA41E815FC1D9000935EF6 /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = Source/Installer/AppDelegate.h; sourceTree = SOURCE_ROOT; };
|
||||
6ACA41E915FC1D9000935EF6 /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = Source/Installer/AppDelegate.m; sourceTree = SOURCE_ROOT; };
|
||||
|
@ -191,6 +212,9 @@
|
|||
D47F7DCF278C0897002F9DD7 /* NonModalAlertWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonModalAlertWindowController.swift; sourceTree = "<group>"; };
|
||||
D47F7DD1278C1263002F9DD7 /* UserOverrideModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UserOverrideModel.h; sourceTree = "<group>"; };
|
||||
D47F7DD2278C1263002F9DD7 /* UserOverrideModel.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = UserOverrideModel.cpp; sourceTree = "<group>"; };
|
||||
D485D3B62796A8A000657FF3 /* McBopomofoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = McBopomofoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D485D3B82796A8A000657FF3 /* PreferencesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesTests.swift; sourceTree = "<group>"; };
|
||||
D485D3BF2796CE3200657FF3 /* VersionUpdateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionUpdateTests.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
@ -218,6 +242,13 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
D485D3B32796A8A000657FF3 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
|
@ -226,6 +257,7 @@
|
|||
children = (
|
||||
D427F766278C9CBD004A2160 /* Packages */,
|
||||
6A0D4EC215FC0D3C00ABF4B3 /* Source */,
|
||||
D485D3B72796A8A000657FF3 /* McBopomofoTests */,
|
||||
6A0D4EA515FC0D2D00ABF4B3 /* Frameworks */,
|
||||
6A0D4EA315FC0D2D00ABF4B3 /* Products */,
|
||||
);
|
||||
|
@ -236,6 +268,7 @@
|
|||
children = (
|
||||
6A0D4EA215FC0D2D00ABF4B3 /* McBopomofo.app */,
|
||||
6ACA41CB15FC1D7500935EF6 /* McBopomofoInstaller.app */,
|
||||
D485D3B62796A8A000657FF3 /* McBopomofoTests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
|
@ -379,6 +412,7 @@
|
|||
6A0D4F4715FC0EB900ABF4B3 /* Resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6A6ED162279764CD0012872E /* Custom Phrase Templates */,
|
||||
6AFF97F0253B299E007F1C49 /* NonModalAlertWindowController.xib */,
|
||||
6A0D4EEE15FC0DA600ABF4B3 /* Images */,
|
||||
6A0D4EF515FC0DA600ABF4B3 /* McBopomofo-Info.plist */,
|
||||
|
@ -399,6 +433,17 @@
|
|||
path = Data;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
6A6ED162279764CD0012872E /* Custom Phrase Templates */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6A6ED1652797650A0012872E /* template-data.txt */,
|
||||
6A6ED1672797650A0012872E /* template-exclude-phrases-plain-bpmf.txt */,
|
||||
6A6ED1692797650A0012872E /* template-exclude-phrases.txt */,
|
||||
6A6ED1632797650A0012872E /* template-phrases-replacement.txt */,
|
||||
);
|
||||
name = "Custom Phrase Templates";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
6ACA41E715FC1D9000935EF6 /* Installer */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -431,6 +476,15 @@
|
|||
name = Packages;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D485D3B72796A8A000657FF3 /* McBopomofoTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D485D3B82796A8A000657FF3 /* PreferencesTests.swift */,
|
||||
D485D3BF2796CE3200657FF3 /* VersionUpdateTests.swift */,
|
||||
);
|
||||
path = McBopomofoTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXLegacyTarget section */
|
||||
|
@ -499,17 +553,40 @@
|
|||
productReference = 6ACA41CB15FC1D7500935EF6 /* McBopomofoInstaller.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
D485D3B52796A8A000657FF3 /* McBopomofoTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = D485D3BE2796A8A000657FF3 /* Build configuration list for PBXNativeTarget "McBopomofoTests" */;
|
||||
buildPhases = (
|
||||
D485D3B22796A8A000657FF3 /* Sources */,
|
||||
D485D3B32796A8A000657FF3 /* Frameworks */,
|
||||
D485D3B42796A8A000657FF3 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
D485D3BB2796A8A000657FF3 /* PBXTargetDependency */,
|
||||
);
|
||||
name = McBopomofoTests;
|
||||
productName = McBopomofoTests;
|
||||
productReference = D485D3B62796A8A000657FF3 /* McBopomofoTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
6A0D4E9415FC0CFA00ABF4B3 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 1320;
|
||||
LastUpgradeCheck = 1310;
|
||||
TargetAttributes = {
|
||||
6A0D4EA115FC0D2D00ABF4B3 = {
|
||||
LastSwiftMigration = 1240;
|
||||
};
|
||||
D485D3B52796A8A000657FF3 = {
|
||||
CreatedOnToolsVersion = 13.2.1;
|
||||
TestTargetID = 6A0D4EA115FC0D2D00ABF4B3;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 6A0D4E9715FC0CFA00ABF4B3 /* Build configuration list for PBXProject "McBopomofo" */;
|
||||
|
@ -531,6 +608,7 @@
|
|||
6A0D4EA115FC0D2D00ABF4B3 /* McBopomofo */,
|
||||
6ACA41CA15FC1D7500935EF6 /* McBopomofoInstaller */,
|
||||
6A38BC2115FC12FD00A8A51F /* Data */,
|
||||
D485D3B52796A8A000657FF3 /* McBopomofoTests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
@ -540,15 +618,19 @@
|
|||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
6A6ED16E2797650A0012872E /* template-exclude-phrases.txt in Resources */,
|
||||
6A0D4F0815FC0DA600ABF4B3 /* Bopomofo.tiff in Resources */,
|
||||
6A0D4F0915FC0DA600ABF4B3 /* Bopomofo@2x.tiff in Resources */,
|
||||
6A6ED16D2797650A0012872E /* template-exclude-phrases-plain-bpmf.txt in Resources */,
|
||||
6A0D4F5315FC0EE100ABF4B3 /* preferences.xib in Resources */,
|
||||
6A0D4F5715FC0EF900ABF4B3 /* InfoPlist.strings in Resources */,
|
||||
6A0D4F5815FC0EF900ABF4B3 /* Localizable.strings in Resources */,
|
||||
6A2E40F6253A69DA00D1AE1D /* Images.xcassets in Resources */,
|
||||
6A38BC1515FC117A00A8A51F /* data.txt in Resources */,
|
||||
6A6ED16B2797650A0012872E /* template-phrases-replacement.txt in Resources */,
|
||||
6AFF97F2253B299E007F1C49 /* NonModalAlertWindowController.xib in Resources */,
|
||||
6AE210B215FC63CC003659FE /* PlainBopomofo.tiff in Resources */,
|
||||
6A6ED16C2797650A0012872E /* template-data.txt in Resources */,
|
||||
6AE210B315FC63CC003659FE /* PlainBopomofo@2x.tiff in Resources */,
|
||||
6AD7CBC815FE555000691B5B /* data-plain-bpmf.txt in Resources */,
|
||||
6A187E2616004C5900466B2E /* MainMenu.xib in Resources */,
|
||||
|
@ -569,6 +651,13 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
D485D3B42796A8A000657FF3 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
|
@ -625,6 +714,15 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
D485D3B22796A8A000657FF3 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D485D3B92796A8A000657FF3 /* PreferencesTests.swift in Sources */,
|
||||
D485D3C02796CE3200657FF3 /* VersionUpdateTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
|
@ -638,6 +736,11 @@
|
|||
target = 6A0D4EA115FC0D2D00ABF4B3 /* McBopomofo */;
|
||||
targetProxy = 6ACA420015FC1DCC00935EF6 /* PBXContainerItemProxy */;
|
||||
};
|
||||
D485D3BB2796A8A000657FF3 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 6A0D4EA115FC0D2D00ABF4B3 /* McBopomofo */;
|
||||
targetProxy = D485D3BA2796A8A000657FF3 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
|
@ -680,6 +783,42 @@
|
|||
name = MainMenu.xib;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
6A6ED1632797650A0012872E /* template-phrases-replacement.txt */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
6A6ED1642797650A0012872E /* Base */,
|
||||
6A6ED1722797651A0012872E /* zh-Hant */,
|
||||
);
|
||||
name = "template-phrases-replacement.txt";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
6A6ED1652797650A0012872E /* template-data.txt */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
6A6ED1662797650A0012872E /* Base */,
|
||||
6A6ED16F279765100012872E /* zh-Hant */,
|
||||
);
|
||||
name = "template-data.txt";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
6A6ED1672797650A0012872E /* template-exclude-phrases-plain-bpmf.txt */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
6A6ED1682797650A0012872E /* Base */,
|
||||
6A6ED170279765140012872E /* zh-Hant */,
|
||||
);
|
||||
name = "template-exclude-phrases-plain-bpmf.txt";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
6A6ED1692797650A0012872E /* template-exclude-phrases.txt */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
6A6ED16A2797650A0012872E /* Base */,
|
||||
6A6ED171279765170012872E /* zh-Hant */,
|
||||
);
|
||||
name = "template-exclude-phrases.txt";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
6ACA41EA15FC1D9000935EF6 /* InfoPlist.strings */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
|
@ -755,6 +894,7 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.10;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
OTHER_CPLUSPLUSFLAGS = (
|
||||
"$(OTHER_CFLAGS)",
|
||||
|
@ -794,6 +934,7 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.10;
|
||||
OTHER_CPLUSPLUSFLAGS = (
|
||||
"$(OTHER_CFLAGS)",
|
||||
"-fcxx-modules",
|
||||
|
@ -1044,6 +1185,88 @@
|
|||
};
|
||||
name = Release;
|
||||
};
|
||||
D485D3BC2796A8A000657FF3 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.10;
|
||||
MARKETING_VERSION = 1.0;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.cerence.McBopomofoTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = macosx;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/McBopomofo.app/Contents/MacOS/McBopomofo";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
D485D3BD2796A8A000657FF3 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.10;
|
||||
MARKETING_VERSION = 1.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.cerence.McBopomofoTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = macosx;
|
||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/McBopomofo.app/Contents/MacOS/McBopomofo";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
|
@ -1083,6 +1306,15 @@
|
|||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
D485D3BE2796A8A000657FF3 /* Build configuration list for PBXNativeTarget "McBopomofoTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
D485D3BC2796A8A000657FF3 /* Debug */,
|
||||
D485D3BD2796A8A000657FF3 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
|
|
|
@ -26,8 +26,19 @@
|
|||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
codeCoverageEnabled = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "D485D3B52796A8A000657FF3"
|
||||
BuildableName = "McBopomofoTests.xctest"
|
||||
BlueprintName = "McBopomofoTests"
|
||||
ReferencedContainer = "container:McBopomofo.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
|
|
|
@ -0,0 +1,257 @@
|
|||
import XCTest
|
||||
@testable import McBopomofo
|
||||
|
||||
class PreferencesTests: XCTestCase {
|
||||
|
||||
override func setUpWithError() throws {
|
||||
Preferences.reset()
|
||||
}
|
||||
|
||||
override func tearDownWithError() throws {
|
||||
Preferences.reset()
|
||||
}
|
||||
|
||||
func testKeyboardLayout() {
|
||||
XCTAssert(Preferences.keyboardLayout == 0)
|
||||
Preferences.keyboardLayout = 1
|
||||
XCTAssert(Preferences.keyboardLayout == 1)
|
||||
}
|
||||
|
||||
func testKeyboardLayoutName() {
|
||||
XCTAssert(Preferences.keyboardLayoutName == "Standard")
|
||||
Preferences.keyboardLayout = 1
|
||||
XCTAssert(Preferences.keyboardLayoutName == "ETen")
|
||||
}
|
||||
|
||||
func testBasisKeyboardLayoutPreferenceKey() {
|
||||
XCTAssert(Preferences.basisKeyboardLayout == "com.apple.keylayout.US")
|
||||
Preferences.basisKeyboardLayout = "com.apple.keylayout.ABC"
|
||||
XCTAssert(Preferences.basisKeyboardLayout == "com.apple.keylayout.ABC")
|
||||
}
|
||||
|
||||
func testFunctionKeyboardLayout() {
|
||||
XCTAssert(Preferences.functionKeyboardLayout == "com.apple.keylayout.US")
|
||||
Preferences.functionKeyboardLayout = "com.apple.keylayout.ABC"
|
||||
XCTAssert(Preferences.functionKeyboardLayout == "com.apple.keylayout.ABC")
|
||||
}
|
||||
|
||||
func testFunctionKeyKeyboardLayoutOverrideIncludeShiftKey() {
|
||||
XCTAssert(Preferences.functionKeyKeyboardLayoutOverrideIncludeShiftKey == false)
|
||||
Preferences.functionKeyKeyboardLayoutOverrideIncludeShiftKey = true
|
||||
XCTAssert(Preferences.functionKeyKeyboardLayoutOverrideIncludeShiftKey == true)
|
||||
}
|
||||
|
||||
func testCandidateTextSize() {
|
||||
XCTAssert(Preferences.candidateListTextSize == 16)
|
||||
|
||||
Preferences.candidateListTextSize = 18
|
||||
XCTAssert(Preferences.candidateListTextSize == 18)
|
||||
|
||||
Preferences.candidateListTextSize = 11
|
||||
XCTAssert(Preferences.candidateListTextSize == 12)
|
||||
Preferences.candidateListTextSize = 197
|
||||
XCTAssert(Preferences.candidateListTextSize == 196)
|
||||
|
||||
Preferences.candidateListTextSize = 12
|
||||
XCTAssert(Preferences.candidateListTextSize == 12)
|
||||
Preferences.candidateListTextSize = 196
|
||||
XCTAssert(Preferences.candidateListTextSize == 196)
|
||||
|
||||
Preferences.candidateListTextSize = 13
|
||||
XCTAssert(Preferences.candidateListTextSize == 13)
|
||||
Preferences.candidateListTextSize = 195
|
||||
XCTAssert(Preferences.candidateListTextSize == 195)
|
||||
}
|
||||
|
||||
func testSelectPhraseAfterCursorAsCandidate() {
|
||||
XCTAssert(Preferences.selectPhraseAfterCursorAsCandidate == false)
|
||||
Preferences.selectPhraseAfterCursorAsCandidate = true
|
||||
XCTAssert(Preferences.selectPhraseAfterCursorAsCandidate == true)
|
||||
}
|
||||
|
||||
func testUseHorizontalCandidateList() {
|
||||
XCTAssert(Preferences.useHorizontalCandidateList == false)
|
||||
Preferences.useHorizontalCandidateList = true
|
||||
XCTAssert(Preferences.useHorizontalCandidateList == true)
|
||||
}
|
||||
|
||||
func testComposingBufferSize() {
|
||||
XCTAssert(Preferences.composingBufferSize == 10)
|
||||
Preferences.composingBufferSize = 4
|
||||
XCTAssert(Preferences.composingBufferSize == 4)
|
||||
Preferences.composingBufferSize = 20
|
||||
XCTAssert(Preferences.composingBufferSize == 20)
|
||||
Preferences.composingBufferSize = 3
|
||||
XCTAssert(Preferences.composingBufferSize == 4)
|
||||
Preferences.composingBufferSize = 21
|
||||
XCTAssert(Preferences.composingBufferSize == 20)
|
||||
Preferences.composingBufferSize = 5
|
||||
XCTAssert(Preferences.composingBufferSize == 5)
|
||||
Preferences.composingBufferSize = 19
|
||||
XCTAssert(Preferences.composingBufferSize == 19)
|
||||
}
|
||||
|
||||
func testChooseCandidateUsingSpace() {
|
||||
XCTAssert(Preferences.chooseCandidateUsingSpace == true)
|
||||
Preferences.chooseCandidateUsingSpace = false
|
||||
XCTAssert(Preferences.chooseCandidateUsingSpace == false)
|
||||
}
|
||||
|
||||
func testChineseConversionEnabled() {
|
||||
XCTAssert(Preferences.chineseConversionEnabled == false)
|
||||
Preferences.chineseConversionEnabled = true
|
||||
XCTAssert(Preferences.chineseConversionEnabled == true)
|
||||
_ = Preferences.toggleChineseConversionEnabled()
|
||||
XCTAssert(Preferences.chineseConversionEnabled == false)
|
||||
}
|
||||
|
||||
func testHalfWidthPunctuationEnabled() {
|
||||
XCTAssert(Preferences.halfWidthPunctuationEnabled == false)
|
||||
Preferences.halfWidthPunctuationEnabled = true
|
||||
XCTAssert(Preferences.halfWidthPunctuationEnabled == true)
|
||||
_ = Preferences.toggleHalfWidthPunctuationEnabled()
|
||||
XCTAssert(Preferences.halfWidthPunctuationEnabled == false)
|
||||
}
|
||||
|
||||
func testEscToCleanInputBuffer() {
|
||||
XCTAssert(Preferences.escToCleanInputBuffer == false)
|
||||
Preferences.escToCleanInputBuffer = true
|
||||
XCTAssert(Preferences.escToCleanInputBuffer == true)
|
||||
}
|
||||
|
||||
func testCandidateTextFontName() {
|
||||
XCTAssert(Preferences.candidateTextFontName == nil)
|
||||
Preferences.candidateTextFontName = "Helvetica"
|
||||
XCTAssert(Preferences.candidateTextFontName == "Helvetica")
|
||||
}
|
||||
|
||||
func testCandidateKeyLabelFontName() {
|
||||
XCTAssert(Preferences.candidateKeyLabelFontName == nil)
|
||||
Preferences.candidateKeyLabelFontName = "Helvetica"
|
||||
XCTAssert(Preferences.candidateKeyLabelFontName == "Helvetica")
|
||||
}
|
||||
|
||||
func testCandidateKeys() {
|
||||
XCTAssert(Preferences.candidateKeys == Preferences.defaultCandidateKeys)
|
||||
Preferences.candidateKeys = "abcd"
|
||||
XCTAssert(Preferences.candidateKeys == "abcd")
|
||||
}
|
||||
|
||||
func testPhraseReplacementEnabledKey() {
|
||||
XCTAssert(Preferences.phraseReplacementEnabled == false)
|
||||
Preferences.phraseReplacementEnabled = true
|
||||
XCTAssert(Preferences.phraseReplacementEnabled == true)
|
||||
}
|
||||
|
||||
func testChineseConversionEngine() {
|
||||
XCTAssert(Preferences.chineseConversionEngine == 0)
|
||||
Preferences.chineseConversionEngine = 1
|
||||
XCTAssert(Preferences.chineseConversionEngine == 1)
|
||||
}
|
||||
|
||||
func testChineseConversionStyle() {
|
||||
XCTAssert(Preferences.chineseConversionStyle == 0)
|
||||
Preferences.chineseConversionStyle = 1
|
||||
XCTAssert(Preferences.chineseConversionStyle == 1)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class CandidateKeyValidationTests: XCTestCase {
|
||||
func testEmpty() {
|
||||
do {
|
||||
try Preferences.validate(candidateKeys: "")
|
||||
XCTFail("exception not thrown")
|
||||
} catch(Preferences.CandidateKeyError.empty) {
|
||||
} catch {
|
||||
XCTFail("exception not thrown")
|
||||
}
|
||||
}
|
||||
|
||||
func testSpaces() {
|
||||
do {
|
||||
try Preferences.validate(candidateKeys: " ")
|
||||
XCTFail("exception not thrown")
|
||||
} catch(Preferences.CandidateKeyError.empty) {
|
||||
} catch {
|
||||
XCTFail("exception not thrown")
|
||||
}
|
||||
}
|
||||
|
||||
func testInvalidKeys() {
|
||||
do {
|
||||
try Preferences.validate(candidateKeys: "中文字元")
|
||||
XCTFail("exception not thrown")
|
||||
} catch(Preferences.CandidateKeyError.invalidCharacters) {
|
||||
} catch {
|
||||
XCTFail("exception not thrown")
|
||||
}
|
||||
}
|
||||
|
||||
func testInvalidLatinLetters() {
|
||||
do {
|
||||
try Preferences.validate(candidateKeys: "üåçøöacpo")
|
||||
XCTFail("exception not thrown")
|
||||
} catch(Preferences.CandidateKeyError.invalidCharacters) {
|
||||
} catch {
|
||||
XCTFail("exception not thrown")
|
||||
}
|
||||
}
|
||||
|
||||
func testSpaceInBetween() {
|
||||
do {
|
||||
try Preferences.validate(candidateKeys: "1 2 3 4")
|
||||
XCTFail("exception not thrown")
|
||||
} catch(Preferences.CandidateKeyError.containSpace) {
|
||||
} catch {
|
||||
XCTFail("exception not thrown")
|
||||
}
|
||||
}
|
||||
|
||||
func testDuplicatedKeys() {
|
||||
do {
|
||||
try Preferences.validate(candidateKeys: "aabbccdd")
|
||||
XCTFail("exception not thrown")
|
||||
} catch(Preferences.CandidateKeyError.duplicatedCharacters) {
|
||||
} catch {
|
||||
XCTFail("exception not thrown")
|
||||
}
|
||||
}
|
||||
|
||||
func testTooShort1() {
|
||||
do {
|
||||
try Preferences.validate(candidateKeys: "abc")
|
||||
XCTFail("exception not thrown")
|
||||
} catch(Preferences.CandidateKeyError.tooShort) {
|
||||
} catch {
|
||||
XCTFail("exception not thrown")
|
||||
}
|
||||
}
|
||||
|
||||
func testTooShort2() {
|
||||
do {
|
||||
try Preferences.validate(candidateKeys: "abcd")
|
||||
} catch {
|
||||
XCTFail("Should be safe")
|
||||
}
|
||||
}
|
||||
|
||||
func testTooLong1() {
|
||||
do {
|
||||
try Preferences.validate(candidateKeys: "qwertyuiopasdfgh")
|
||||
XCTFail("exception not thrown")
|
||||
} catch(Preferences.CandidateKeyError.tooLong) {
|
||||
} catch {
|
||||
XCTFail("exception not thrown")
|
||||
}
|
||||
}
|
||||
|
||||
func testTooLong2() {
|
||||
do {
|
||||
try Preferences.validate(candidateKeys: "qwertyuiopasdfg")
|
||||
}
|
||||
catch {
|
||||
XCTFail("Should be safe")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
import XCTest
|
||||
@testable import McBopomofo
|
||||
|
||||
class VersionUpdateApiTests: XCTestCase {
|
||||
func testFetchVersionUpdateInfo() {
|
||||
let exp = self.expectation(description: "wait for 3 seconds")
|
||||
_ = VersionUpdateApi.check(forced: true) { result in
|
||||
exp.fulfill()
|
||||
switch result {
|
||||
case .success(_):
|
||||
break
|
||||
case .failure(let error):
|
||||
XCTFail(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
self.wait(for: [exp], timeout: 20.0)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -21,5 +21,9 @@ let package = Package(
|
|||
.target(
|
||||
name: "CandidateUI",
|
||||
dependencies: []),
|
||||
.testTarget(
|
||||
name: "CandidateUITests",
|
||||
dependencies: ["CandidateUI"]),
|
||||
|
||||
]
|
||||
)
|
||||
|
|
|
@ -1,14 +1,4 @@
|
|||
//
|
||||
// CandidateController.swift
|
||||
//
|
||||
// Copyright (c) 2011 The McBopomofo Project.
|
||||
//
|
||||
// Contributors:
|
||||
// Mengjuei Hsieh (@mjhsieh)
|
||||
// Weizhong Yang (@zonble)
|
||||
//
|
||||
// Based on the Syrup Project and the Formosana Library
|
||||
// by Lukhnos Liu (@lukhnos).
|
||||
// Copyright (c) 2022 and onwards The McBopomofo Authors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person
|
||||
// obtaining a copy of this software and associated documentation
|
||||
|
@ -30,20 +20,23 @@
|
|||
// 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 (VTCandidateControllerDelegate)
|
||||
@objc(VTCandidateControllerDelegate)
|
||||
public protocol CandidateControllerDelegate: AnyObject {
|
||||
func candidateCountForController(_ controller: CandidateController) -> UInt
|
||||
func candidateController(_ controller: CandidateController, candidateAtIndex index: UInt) -> String
|
||||
func candidateController(_ controller: CandidateController, didSelectCandidateAtIndex index: UInt)
|
||||
}
|
||||
|
||||
@objc (VTCandidateController)
|
||||
@objc(VTCandidateController)
|
||||
public class CandidateController: NSWindowController {
|
||||
@objc public weak var delegate: CandidateControllerDelegate?
|
||||
@objc public weak var delegate: CandidateControllerDelegate? {
|
||||
didSet {
|
||||
reloadData()
|
||||
}
|
||||
}
|
||||
@objc public var selectedCandidateIndex: UInt = UInt.max
|
||||
@objc public var visible: Bool = false {
|
||||
didSet {
|
||||
|
@ -95,7 +88,17 @@ public class CandidateController: NSWindowController {
|
|||
UInt.max
|
||||
}
|
||||
|
||||
@objc (setWindowTopLeftPoint:bottomOutOfScreenAdjustmentHeight:)
|
||||
/// Sets the location of the candidate window.
|
||||
///
|
||||
/// Please note that the method has side effects that modifies
|
||||
/// `windowTopLeftPoint` to make the candidate window to stay in at least
|
||||
/// in a screen.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - windowTopLeftPoint: The given location.
|
||||
/// - height: The height that helps the window not to be out of the bottom
|
||||
/// of a screen.
|
||||
@objc(setWindowTopLeftPoint:bottomOutOfScreenAdjustmentHeight:)
|
||||
public func set(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight height: CGFloat) {
|
||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) {
|
||||
self.doSet(windowTopLeftPoint: windowTopLeftPoint, bottomOutOfScreenAdjustmentHeight: height)
|
||||
|
|
|
@ -1,14 +1,4 @@
|
|||
//
|
||||
// HorizontalCandidateController.swift
|
||||
//
|
||||
// Copyright (c) 2011 The McBopomofo Project.
|
||||
//
|
||||
// Contributors:
|
||||
// Mengjuei Hsieh (@mjhsieh)
|
||||
// Weizhong Yang (@zonble)
|
||||
//
|
||||
// Based on the Syrup Project and the Formosana Library
|
||||
// by Lukhnos Liu (@lukhnos).
|
||||
// Copyright (c) 2022 and onwards The McBopomofo Authors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person
|
||||
// obtaining a copy of this software and associated documentation
|
||||
|
@ -30,7 +20,6 @@
|
|||
// 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
|
||||
|
||||
|
@ -64,7 +53,7 @@ fileprivate class HorizontalCandidateView: NSView {
|
|||
return result
|
||||
}
|
||||
|
||||
@objc (setKeyLabels:displayedCandidates:)
|
||||
@objc(setKeyLabels:displayedCandidates:)
|
||||
func set(keyLabels labels: [String], displayedCandidates candidates: [String]) {
|
||||
let count = min(labels.count, candidates.count)
|
||||
keyLabels = Array(labels[0..<count])
|
||||
|
@ -81,7 +70,7 @@ fileprivate class HorizontalCandidateView: NSView {
|
|||
elementWidths = newWidths
|
||||
}
|
||||
|
||||
@objc (setKeyLabelFont:candidateFont:)
|
||||
@objc(setKeyLabelFont:candidateFont:)
|
||||
func set(keyLabelFont labelFont: NSFont, candidateFont: NSFont) {
|
||||
let paraStyle = NSMutableParagraphStyle()
|
||||
paraStyle.setParagraphStyle(NSParagraphStyle.default)
|
||||
|
@ -192,7 +181,7 @@ fileprivate class HorizontalCandidateView: NSView {
|
|||
}
|
||||
}
|
||||
|
||||
@objc (VTHorizontalCandidateController)
|
||||
@objc(VTHorizontalCandidateController)
|
||||
public class HorizontalCandidateController: CandidateController {
|
||||
private var candidateView: HorizontalCandidateView
|
||||
private var prevPageButton: NSButton
|
||||
|
@ -364,7 +353,7 @@ extension HorizontalCandidateController {
|
|||
|
||||
if pageCount > 1 {
|
||||
var buttonRect = nextPageButton.frame
|
||||
var spacing:CGFloat = 0.0
|
||||
var spacing: CGFloat = 0.0
|
||||
|
||||
if newSize.height < 40.0 {
|
||||
buttonRect.size.height = floor(newSize.height / 2)
|
||||
|
|
|
@ -1,14 +1,4 @@
|
|||
//
|
||||
// VerticalCandidateController.swift
|
||||
//
|
||||
// Copyright (c) 2011 The McBopomofo Project.
|
||||
//
|
||||
// Contributors:
|
||||
// Mengjuei Hsieh (@mjhsieh)
|
||||
// Weizhong Yang (@zonble)
|
||||
//
|
||||
// Based on the Syrup Project and the Formosana Library
|
||||
// by Lukhnos Liu (@lukhnos).
|
||||
// Copyright (c) 2022 and onwards The McBopomofo Authors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person
|
||||
// obtaining a copy of this software and associated documentation
|
||||
|
@ -30,7 +20,6 @@
|
|||
// 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
|
||||
|
||||
|
@ -91,13 +80,13 @@ fileprivate class VerticalCandidateTableView: NSTableView {
|
|||
}
|
||||
}
|
||||
|
||||
private let kCandidateTextPadding:CGFloat = 24.0
|
||||
private let kCandidateTextLeftMargin:CGFloat = 8.0
|
||||
private let kCandidateTextPaddingWithMandatedTableViewPadding:CGFloat = 18.0
|
||||
private let kCandidateTextLeftMarginWithMandatedTableViewPadding:CGFloat = 0.0
|
||||
private let kCandidateTextPadding: CGFloat = 24.0
|
||||
private let kCandidateTextLeftMargin: CGFloat = 8.0
|
||||
private let kCandidateTextPaddingWithMandatedTableViewPadding: CGFloat = 18.0
|
||||
private let kCandidateTextLeftMarginWithMandatedTableViewPadding: CGFloat = 0.0
|
||||
|
||||
|
||||
@objc (VTVerticalCandidateController)
|
||||
@objc(VTVerticalCandidateController)
|
||||
public class VerticalCandidateController: CandidateController {
|
||||
private var keyLabelStripView: VerticalKeyLabelStripView
|
||||
private var scrollView: NSScrollView
|
||||
|
@ -309,7 +298,13 @@ extension VerticalCandidateController: NSTableViewDataSource, NSTableViewDelegat
|
|||
if selectedRow != -1 {
|
||||
// keep track of the highlighted index in the key label strip
|
||||
let firstVisibleRow = tableView.row(at: scrollView.documentVisibleRect.origin)
|
||||
// firstVisibleRow cannot be larger than selectedRow.
|
||||
if selectedRow >= firstVisibleRow {
|
||||
keyLabelStripView.highlightedIndex = UInt(selectedRow - firstVisibleRow)
|
||||
} else {
|
||||
keyLabelStripView.highlightedIndex = UInt.max
|
||||
}
|
||||
|
||||
keyLabelStripView.setNeedsDisplay(keyLabelStripView.frame)
|
||||
|
||||
// fix a subtle OS X "bug" that, since we force the scroller to appear,
|
||||
|
@ -343,7 +338,7 @@ extension VerticalCandidateController: NSTableViewDataSource, NSTableViewDelegat
|
|||
|
||||
var newIndex = selectedCandidateIndex
|
||||
if forward {
|
||||
if newIndex == itemCount - 1 {
|
||||
if newIndex >= itemCount - 1 {
|
||||
return false
|
||||
}
|
||||
newIndex = min(newIndex + labelCount, itemCount - 1)
|
||||
|
@ -371,8 +366,12 @@ extension VerticalCandidateController: NSTableViewDataSource, NSTableViewDelegat
|
|||
return false
|
||||
}
|
||||
var newIndex = selectedCandidateIndex
|
||||
if newIndex == UInt.max {
|
||||
return false
|
||||
}
|
||||
|
||||
if forward {
|
||||
if newIndex == itemCount - 1 {
|
||||
if newIndex >= itemCount - 1 {
|
||||
return false
|
||||
}
|
||||
newIndex += 1
|
||||
|
|
|
@ -0,0 +1,142 @@
|
|||
import XCTest
|
||||
@testable import CandidateUI
|
||||
|
||||
class HorizontalCandidateControllerTests: XCTestCase {
|
||||
|
||||
class Mock: CandidateControllerDelegate {
|
||||
let candidates = ["A", "B", "C", "D", "E", "F", "G", "H"]
|
||||
var selected: String?
|
||||
|
||||
func candidateCountForController(_ controller: CandidateController) -> UInt {
|
||||
UInt(candidates.count)
|
||||
}
|
||||
|
||||
func candidateController(_ controller: CandidateController, candidateAtIndex index: UInt) -> String {
|
||||
candidates[Int(index)]
|
||||
}
|
||||
|
||||
func candidateController(_ controller: CandidateController, didSelectCandidateAtIndex index: UInt) {
|
||||
selected = candidates[Int(index)]
|
||||
}
|
||||
}
|
||||
|
||||
func testPositioning1() {
|
||||
let controller = HorizontalCandidateController()
|
||||
let mock = Mock()
|
||||
controller.delegate = mock
|
||||
controller.keyLabels = ["1", "2", "3", "4"]
|
||||
controller.reloadData()
|
||||
controller.visible = true
|
||||
controller.set(windowTopLeftPoint: NSPoint(x: -100, y: 0), bottomOutOfScreenAdjustmentHeight: 10)
|
||||
let exp = expectation(description: "wait")
|
||||
_ = XCTWaiter.wait(for: [exp], timeout: 0.2)
|
||||
XCTAssert(controller.window?.frame.minX ?? -1 >= 0)
|
||||
}
|
||||
|
||||
func testPositioning2() {
|
||||
let controller = HorizontalCandidateController()
|
||||
let mock = Mock()
|
||||
controller.delegate = mock
|
||||
controller.keyLabels = ["1", "2", "3", "4"]
|
||||
controller.reloadData()
|
||||
controller.visible = true
|
||||
let screenRect = NSScreen.main?.frame ?? NSRect.zero
|
||||
controller.set(windowTopLeftPoint: NSPoint(x: screenRect.maxX + 100, y: screenRect.maxY + 100), bottomOutOfScreenAdjustmentHeight: 10)
|
||||
let exp = expectation(description: "wait")
|
||||
_ = XCTWaiter.wait(for: [exp], timeout: 0.2)
|
||||
XCTAssert(controller.window?.frame.maxX ?? CGFloat.greatestFiniteMagnitude <= screenRect.maxX)
|
||||
XCTAssert(controller.window?.frame.maxY ?? CGFloat.greatestFiniteMagnitude <= screenRect.maxY)
|
||||
}
|
||||
|
||||
func testReloadData() {
|
||||
let controller = HorizontalCandidateController()
|
||||
let mock = Mock()
|
||||
controller.delegate = mock
|
||||
controller.keyLabels = ["1", "2", "3", "4"]
|
||||
controller.reloadData()
|
||||
XCTAssert(controller.selectedCandidateIndex == 0)
|
||||
}
|
||||
|
||||
func testHighlightNextCandidate() {
|
||||
let controller = HorizontalCandidateController()
|
||||
let mock = Mock()
|
||||
controller.keyLabels = ["1", "2", "3", "4"]
|
||||
controller.delegate = mock
|
||||
var result = controller.highlightNextCandidate()
|
||||
XCTAssert(result == true)
|
||||
XCTAssert(controller.selectedCandidateIndex == 1)
|
||||
result = controller.highlightNextCandidate()
|
||||
XCTAssert(result == true)
|
||||
XCTAssert(controller.selectedCandidateIndex == 2)
|
||||
result = controller.highlightNextCandidate()
|
||||
XCTAssert(result == true)
|
||||
XCTAssert(controller.selectedCandidateIndex == 3)
|
||||
result = controller.highlightNextCandidate()
|
||||
XCTAssert(result == true)
|
||||
XCTAssert(controller.selectedCandidateIndex == 4)
|
||||
result = controller.highlightNextCandidate()
|
||||
XCTAssert(result == true)
|
||||
XCTAssert(controller.selectedCandidateIndex == 5)
|
||||
result = controller.highlightNextCandidate()
|
||||
XCTAssert(result == true)
|
||||
XCTAssert(controller.selectedCandidateIndex == 6)
|
||||
result = controller.highlightNextCandidate()
|
||||
XCTAssert(result == true)
|
||||
XCTAssert(controller.selectedCandidateIndex == 7)
|
||||
result = controller.highlightNextCandidate()
|
||||
XCTAssert(result == false)
|
||||
XCTAssert(controller.selectedCandidateIndex == 7)
|
||||
}
|
||||
|
||||
func testHighlightPreviousCandidate() {
|
||||
let controller = HorizontalCandidateController()
|
||||
let mock = Mock()
|
||||
controller.keyLabels = ["1", "2", "3", "4"]
|
||||
controller.delegate = mock
|
||||
_ = controller.showNextPage()
|
||||
XCTAssert(controller.selectedCandidateIndex == 4)
|
||||
var result = controller.highlightPreviousCandidate()
|
||||
XCTAssert(result == true)
|
||||
XCTAssert(controller.selectedCandidateIndex == 3)
|
||||
result = controller.highlightPreviousCandidate()
|
||||
XCTAssert(result == true)
|
||||
XCTAssert(controller.selectedCandidateIndex == 2)
|
||||
result = controller.highlightPreviousCandidate()
|
||||
XCTAssert(result == true)
|
||||
XCTAssert(controller.selectedCandidateIndex == 1)
|
||||
result = controller.highlightPreviousCandidate()
|
||||
XCTAssert(result == true)
|
||||
XCTAssert(controller.selectedCandidateIndex == 0)
|
||||
result = controller.highlightPreviousCandidate()
|
||||
XCTAssert(result == false)
|
||||
XCTAssert(controller.selectedCandidateIndex == 0)
|
||||
}
|
||||
|
||||
func testShowNextPage() {
|
||||
let controller = HorizontalCandidateController()
|
||||
let mock = Mock()
|
||||
controller.keyLabels = ["1", "2", "3", "4"]
|
||||
_ = controller.delegate = mock
|
||||
var result = controller.showNextPage()
|
||||
XCTAssert(result == true)
|
||||
XCTAssert(controller.selectedCandidateIndex == 4)
|
||||
result = controller.showNextPage()
|
||||
XCTAssert(result == false)
|
||||
XCTAssert(controller.selectedCandidateIndex == 4)
|
||||
}
|
||||
|
||||
func testShowPreviousPage() {
|
||||
let controller = HorizontalCandidateController()
|
||||
let mock = Mock()
|
||||
controller.keyLabels = ["1", "2", "3", "4"]
|
||||
controller.delegate = mock
|
||||
_ = controller.showNextPage()
|
||||
var result = controller.showPreviousPage()
|
||||
XCTAssert(result == true)
|
||||
XCTAssert(controller.selectedCandidateIndex == 0)
|
||||
result = controller.showPreviousPage()
|
||||
XCTAssert(result == false)
|
||||
XCTAssert(controller.selectedCandidateIndex == 0)
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
import XCTest
|
||||
@testable import CandidateUI
|
||||
|
||||
class VerticalCandidateControllerTests: XCTestCase {
|
||||
|
||||
class Mock: CandidateControllerDelegate {
|
||||
let candidates = ["A", "B", "C", "D", "E", "F", "G", "H"]
|
||||
var selected: String?
|
||||
|
||||
func candidateCountForController(_ controller: CandidateController) -> UInt {
|
||||
UInt(candidates.count)
|
||||
}
|
||||
|
||||
func candidateController(_ controller: CandidateController, candidateAtIndex index: UInt) -> String {
|
||||
candidates[Int(index)]
|
||||
}
|
||||
|
||||
func candidateController(_ controller: CandidateController, didSelectCandidateAtIndex index: UInt) {
|
||||
selected = candidates[Int(index)]
|
||||
}
|
||||
}
|
||||
|
||||
func testPositioning1() {
|
||||
let controller = HorizontalCandidateController()
|
||||
let mock = Mock()
|
||||
controller.delegate = mock
|
||||
controller.keyLabels = ["1", "2", "3", "4"]
|
||||
controller.reloadData()
|
||||
controller.visible = true
|
||||
controller.set(windowTopLeftPoint: NSPoint(x: -100, y: 0), bottomOutOfScreenAdjustmentHeight: 10)
|
||||
let exp = expectation(description: "wait")
|
||||
_ = XCTWaiter.wait(for: [exp], timeout: 0.2)
|
||||
XCTAssert(controller.window?.frame.minX ?? -1 >= 0)
|
||||
}
|
||||
|
||||
func testPositioning2() {
|
||||
let controller = HorizontalCandidateController()
|
||||
let mock = Mock()
|
||||
controller.delegate = mock
|
||||
controller.keyLabels = ["1", "2", "3", "4"]
|
||||
controller.reloadData()
|
||||
controller.visible = true
|
||||
let screenRect = NSScreen.main?.frame ?? NSRect.zero
|
||||
controller.set(windowTopLeftPoint: NSPoint(x: screenRect.maxX + 100, y: screenRect.maxY + 100), bottomOutOfScreenAdjustmentHeight: 10)
|
||||
let exp = expectation(description: "wait")
|
||||
_ = XCTWaiter.wait(for: [exp], timeout: 0.2)
|
||||
XCTAssert(controller.window?.frame.maxX ?? CGFloat.greatestFiniteMagnitude <= screenRect.maxX)
|
||||
XCTAssert(controller.window?.frame.maxY ?? CGFloat.greatestFiniteMagnitude <= screenRect.maxY)
|
||||
}
|
||||
|
||||
func testReloadData() {
|
||||
let controller = VerticalCandidateController()
|
||||
let mock = Mock()
|
||||
controller.delegate = mock
|
||||
controller.keyLabels = ["1", "2", "3", "4"]
|
||||
controller.reloadData()
|
||||
XCTAssert(controller.selectedCandidateIndex == 0)
|
||||
}
|
||||
|
||||
func testHighlightNextCandidate() {
|
||||
let controller = VerticalCandidateController()
|
||||
let mock = Mock()
|
||||
controller.keyLabels = ["1", "2", "3", "4"]
|
||||
controller.delegate = mock
|
||||
controller.reloadData()
|
||||
var result = controller.highlightNextCandidate()
|
||||
XCTAssert(result == true)
|
||||
XCTAssert(controller.selectedCandidateIndex == 1)
|
||||
result = controller.highlightNextCandidate()
|
||||
XCTAssert(result == true)
|
||||
XCTAssert(controller.selectedCandidateIndex == 2)
|
||||
result = controller.highlightNextCandidate()
|
||||
XCTAssert(result == true)
|
||||
XCTAssert(controller.selectedCandidateIndex == 3)
|
||||
result = controller.highlightNextCandidate()
|
||||
XCTAssert(result == true)
|
||||
XCTAssert(controller.selectedCandidateIndex == 4)
|
||||
result = controller.highlightNextCandidate()
|
||||
XCTAssert(result == true)
|
||||
XCTAssert(controller.selectedCandidateIndex == 5)
|
||||
result = controller.highlightNextCandidate()
|
||||
XCTAssert(result == true)
|
||||
XCTAssert(controller.selectedCandidateIndex == 6)
|
||||
result = controller.highlightNextCandidate()
|
||||
XCTAssert(result == true)
|
||||
XCTAssert(controller.selectedCandidateIndex == 7)
|
||||
result = controller.highlightNextCandidate()
|
||||
XCTAssert(result == false)
|
||||
XCTAssert(controller.selectedCandidateIndex == 7)
|
||||
}
|
||||
|
||||
func testHighlightPreviousCandidate() {
|
||||
let controller = VerticalCandidateController()
|
||||
let mock = Mock()
|
||||
controller.keyLabels = ["1", "2", "3", "4"]
|
||||
controller.delegate = mock
|
||||
_ = controller.showNextPage()
|
||||
XCTAssert(controller.selectedCandidateIndex == 4)
|
||||
var result = controller.highlightPreviousCandidate()
|
||||
XCTAssert(result == true)
|
||||
XCTAssert(controller.selectedCandidateIndex == 3)
|
||||
result = controller.highlightPreviousCandidate()
|
||||
XCTAssert(result == true)
|
||||
XCTAssert(controller.selectedCandidateIndex == 2)
|
||||
result = controller.highlightPreviousCandidate()
|
||||
XCTAssert(result == true)
|
||||
XCTAssert(controller.selectedCandidateIndex == 1)
|
||||
result = controller.highlightPreviousCandidate()
|
||||
XCTAssert(result == true)
|
||||
XCTAssert(controller.selectedCandidateIndex == 0)
|
||||
result = controller.highlightPreviousCandidate()
|
||||
XCTAssert(result == false)
|
||||
XCTAssert(controller.selectedCandidateIndex == 0)
|
||||
}
|
||||
|
||||
func testShowNextPage() {
|
||||
let controller = VerticalCandidateController()
|
||||
let mock = Mock()
|
||||
controller.keyLabels = ["1", "2", "3", "4"]
|
||||
_ = controller.delegate = mock
|
||||
var result = controller.showNextPage()
|
||||
XCTAssert(result == true)
|
||||
XCTAssert(controller.selectedCandidateIndex == 4)
|
||||
result = controller.showNextPage()
|
||||
XCTAssert(result == true)
|
||||
XCTAssert(controller.selectedCandidateIndex == 7)
|
||||
result = controller.showNextPage()
|
||||
XCTAssert(result == false)
|
||||
XCTAssert(controller.selectedCandidateIndex == 7)
|
||||
}
|
||||
|
||||
func testShowPreviousPage() {
|
||||
let controller = VerticalCandidateController()
|
||||
let mock = Mock()
|
||||
controller.keyLabels = ["1", "2", "3", "4"]
|
||||
controller.delegate = mock
|
||||
_ = controller.showNextPage()
|
||||
var result = controller.showPreviousPage()
|
||||
XCTAssert(result == true)
|
||||
XCTAssert(controller.selectedCandidateIndex == 0)
|
||||
result = controller.showPreviousPage()
|
||||
XCTAssert(result == false)
|
||||
XCTAssert(controller.selectedCandidateIndex == 0)
|
||||
}
|
||||
|
||||
}
|
|
@ -1,14 +1,4 @@
|
|||
//
|
||||
// OVInputSourceHelper.swift
|
||||
//
|
||||
// Copyright (c) 2011 The McBopomofo Project.
|
||||
//
|
||||
// Contributors:
|
||||
// Mengjuei Hsieh (@mjhsieh)
|
||||
// Weizhong Yang (@zonble)
|
||||
//
|
||||
// Based on the Syrup Project and the Formosana Library
|
||||
// by Lukhnos Liu (@lukhnos).
|
||||
// Copyright (c) 2022 and onwards The McBopomofo Authors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person
|
||||
// obtaining a copy of this software and associated documentation
|
||||
|
@ -30,7 +20,6 @@
|
|||
// 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
|
||||
import Carbon
|
||||
|
@ -46,7 +35,7 @@ public class InputSourceHelper: NSObject {
|
|||
TISCreateInputSourceList(nil, true).takeRetainedValue() as! [TISInputSource]
|
||||
}
|
||||
|
||||
@objc (inputSourceForProperty:stringValue:)
|
||||
@objc(inputSourceForProperty:stringValue:)
|
||||
public static func inputSource(for propertyKey: CFString, stringValue: String) -> TISInputSource? {
|
||||
let stringID = CFStringGetTypeID()
|
||||
for source in allInstalledInputSources() {
|
||||
|
@ -64,12 +53,12 @@ public class InputSourceHelper: NSObject {
|
|||
return nil
|
||||
}
|
||||
|
||||
@objc (inputSourceForInputSourceID:)
|
||||
@objc(inputSourceForInputSourceID:)
|
||||
public static func inputSource(for sourceID: String) -> TISInputSource? {
|
||||
inputSource(for: kTISPropertyInputSourceID, stringValue: sourceID)
|
||||
}
|
||||
|
||||
@objc (inputSourceEnabled:)
|
||||
@objc(inputSourceEnabled:)
|
||||
public static func inputSourceEnabled(for source: TISInputSource) -> Bool {
|
||||
if let valuePts = TISGetInputSourceProperty(source, kTISPropertyInputSourceIsEnabled) {
|
||||
let value = Unmanaged<CFBoolean>.fromOpaque(valuePts).takeUnretainedValue()
|
||||
|
@ -78,13 +67,13 @@ public class InputSourceHelper: NSObject {
|
|||
return false
|
||||
}
|
||||
|
||||
@objc (enableInputSource:)
|
||||
@objc(enableInputSource:)
|
||||
public static func enable(inputSource: TISInputSource) -> Bool {
|
||||
let status = TISEnableInputSource(inputSource)
|
||||
return status == noErr
|
||||
}
|
||||
|
||||
@objc (enableAllInputModesForInputSourceBundleID:)
|
||||
@objc(enableAllInputModesForInputSourceBundleID:)
|
||||
public static func enableAllInputMode(for inputSourceBundleD: String) -> Bool {
|
||||
var enabled = false
|
||||
for source in allInstalledInputSources() {
|
||||
|
@ -105,7 +94,7 @@ public class InputSourceHelper: NSObject {
|
|||
return enabled
|
||||
}
|
||||
|
||||
@objc (enableInputMode:forInputSourceBundleID:)
|
||||
@objc(enableInputMode:forInputSourceBundleID:)
|
||||
public static func enable(inputMode modeID: String, for bundleID: String) -> Bool {
|
||||
for source in allInstalledInputSources() {
|
||||
guard let bundleIDPtr = TISGetInputSourceProperty(source, kTISPropertyBundleID),
|
||||
|
@ -126,13 +115,13 @@ public class InputSourceHelper: NSObject {
|
|||
|
||||
}
|
||||
|
||||
@objc (disableInputSource:)
|
||||
@objc(disableInputSource:)
|
||||
public static func disable(inputSource: TISInputSource) -> Bool {
|
||||
let status = TISDisableInputSource(inputSource)
|
||||
return status == noErr
|
||||
}
|
||||
|
||||
@objc (registerInputSource:)
|
||||
@objc(registerInputSource:)
|
||||
public static func registerTnputSource(at url: URL) -> Bool {
|
||||
let status = TISRegisterInputSource(url as CFURL)
|
||||
return status == noErr
|
||||
|
|
|
@ -1,3 +1,26 @@
|
|||
// Copyright (c) 2022 and onwards The McBopomofo Authors.
|
||||
//
|
||||
// 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:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// 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
|
||||
|
||||
private protocol NotifierWindowDelegate: AnyObject {
|
||||
|
|
|
@ -1,3 +1,26 @@
|
|||
// Copyright (c) 2021 and onwards The McBopomofo Authors.
|
||||
//
|
||||
// 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:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// 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 Foundation
|
||||
import OpenCC
|
||||
|
||||
|
|
|
@ -1,3 +1,26 @@
|
|||
// Copyright (c) 2022 and onwards The McBopomofo Authors.
|
||||
//
|
||||
// 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:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// 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 XCTest
|
||||
@testable import OpenCCBridge
|
||||
|
||||
|
|
|
@ -1,3 +1,26 @@
|
|||
// Copyright (c) 2022 and onwards The McBopomofo Authors.
|
||||
//
|
||||
// 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:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// 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
|
||||
|
||||
public class TooltipController: NSWindowController {
|
||||
|
@ -46,12 +69,47 @@ public class TooltipController: NSWindowController {
|
|||
window?.orderOut(nil)
|
||||
}
|
||||
|
||||
private func set(windowLocation location: NSPoint) {
|
||||
var newPoint = location
|
||||
if location.y > 5 {
|
||||
newPoint.y -= 5
|
||||
private func set(windowLocation windowTopLeftPoint: NSPoint) {
|
||||
|
||||
var adjustedPoint = windowTopLeftPoint
|
||||
adjustedPoint.y -= 5
|
||||
|
||||
var screenFrame = NSScreen.main?.visibleFrame ?? NSRect.zero
|
||||
for screen in NSScreen.screens {
|
||||
let frame = screen.visibleFrame
|
||||
if windowTopLeftPoint.x >= frame.minX &&
|
||||
windowTopLeftPoint.x <= frame.maxX &&
|
||||
windowTopLeftPoint.y >= frame.minY &&
|
||||
windowTopLeftPoint.y <= frame.maxY {
|
||||
screenFrame = frame
|
||||
break
|
||||
}
|
||||
window?.setFrameTopLeftPoint(newPoint)
|
||||
}
|
||||
|
||||
let windowSize = window?.frame.size ?? NSSize.zero
|
||||
|
||||
// bottom beneath the screen?
|
||||
if adjustedPoint.y - windowSize.height < screenFrame.minY {
|
||||
adjustedPoint.y = screenFrame.minY + windowSize.height
|
||||
}
|
||||
|
||||
// top over the screen?
|
||||
if adjustedPoint.y >= screenFrame.maxY {
|
||||
adjustedPoint.y = screenFrame.maxY - 1.0
|
||||
}
|
||||
|
||||
// right
|
||||
if adjustedPoint.x + windowSize.width >= screenFrame.maxX {
|
||||
adjustedPoint.x = screenFrame.maxX - windowSize.width
|
||||
}
|
||||
|
||||
// left
|
||||
if adjustedPoint.x < screenFrame.minX {
|
||||
adjustedPoint.x = screenFrame.minX
|
||||
}
|
||||
|
||||
window?.setFrameTopLeftPoint(adjustedPoint)
|
||||
|
||||
}
|
||||
|
||||
private func adjustSize() {
|
||||
|
|
|
@ -1,3 +1,26 @@
|
|||
// Copyright (c) 2022 and onwards The McBopomofo Authors.
|
||||
//
|
||||
// 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:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// 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 "VXHanConvert.h"
|
||||
|
||||
const size_t vxSC2TCTableSize = 8189;
|
||||
|
|
|
@ -1,3 +1,26 @@
|
|||
// Copyright (c) 2022 and onwards The McBopomofo Authors.
|
||||
//
|
||||
// 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:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// 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 <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
|
|
@ -1,3 +1,26 @@
|
|||
// Copyright (c) 2022 and onwards The McBopomofo Authors.
|
||||
//
|
||||
// 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:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// 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 XCTest
|
||||
@testable import VXHanConvert
|
||||
|
||||
|
|
|
@ -18,6 +18,14 @@
|
|||
|
||||
要注意的是 macOS 可能會限制同一次 login session 能 kill 同一個輸入法 process 的次數(安裝程式透過 kill input method process 來讓新版的輸入法生效)。如果安裝若干次後,發現程式修改的結果並沒有出現,或甚至輸入法已無法再選用,只要登出目前帳號再重新登入即可。
|
||||
|
||||
## 社群公約
|
||||
|
||||
歡迎小麥注音用戶回報問題與指教,也歡迎大家參與小麥注音開發。
|
||||
|
||||
首先,請參考我們在「[常見問題](https://github.com/openvanilla/McBopomofo/wiki/常見問題)」中所提「[我可以怎麼參與小麥注音?](https://github.com/openvanilla/McBopomofo/wiki/常見問題#我可以怎麼參與小麥注音)」一節的說明。
|
||||
|
||||
我們採用了 GitHub 的[通用社群公約](https://github.com/openvanilla/McBopomofo/blob/master/CODE_OF_CONDUCT.md)。公約的中文版請參考[這裡的翻譯](https://www.contributor-covenant.org/zh-tw/version/1/4/code-of-conduct/)。
|
||||
|
||||
## 軟體授權
|
||||
|
||||
本專案採用 MIT License 釋出,使用者可自由使用、散播本軟體,惟散播時必須完整保留版權聲明及軟體授權([詳全文](https://github.com/openvanilla/McBopomofo/blob/master/LICENSE.txt))。
|
||||
|
|
|
@ -1,14 +1,4 @@
|
|||
//
|
||||
// AppDelegate.swift
|
||||
//
|
||||
// Copyright (c) 2011 The McBopomofo Project.
|
||||
//
|
||||
// Contributors:
|
||||
// Mengjuei Hsieh (@mjhsieh)
|
||||
// Weizhong Yang (@zonble)
|
||||
//
|
||||
// Based on the Syrup Project and the Formosana Library
|
||||
// by Lukhnos Liu (@lukhnos).
|
||||
// Copyright (c) 2022 and onwards The McBopomofo Authors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person
|
||||
// obtaining a copy of this software and associated documentation
|
||||
|
@ -30,7 +20,6 @@
|
|||
// 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
|
||||
import InputMethodKit
|
||||
|
@ -42,7 +31,119 @@ private let kUpdateInfoSiteKey = "UpdateInfoSite"
|
|||
private let kNextCheckInterval: TimeInterval = 86400.0
|
||||
private let kTimeoutInterval: TimeInterval = 60.0
|
||||
|
||||
@objc (AppDelegate)
|
||||
struct VersionUpdateReport {
|
||||
var siteUrl: URL?
|
||||
var currentShortVersion: String = ""
|
||||
var currentVersion: String = ""
|
||||
var remoteShortVersion: String = ""
|
||||
var remoteVersion: String = ""
|
||||
var versionDescription: String = ""
|
||||
}
|
||||
|
||||
enum VersionUpdateApiResult {
|
||||
case shouldUpdate(report: VersionUpdateReport)
|
||||
case noNeedToUpdate
|
||||
case ignored
|
||||
}
|
||||
|
||||
enum VersionUpdateApiError: Error, LocalizedError {
|
||||
case connectionError(message: String)
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .connectionError(let message):
|
||||
return String(format: NSLocalizedString("There may be no internet connection or the server failed to respond.\n\nError message: %@", comment: ""), message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct VersionUpdateApi {
|
||||
static func check(forced: Bool, callback: @escaping (Result<VersionUpdateApiResult, Error>) -> ()) -> URLSessionTask? {
|
||||
guard let infoDict = Bundle.main.infoDictionary,
|
||||
let updateInfoURLString = infoDict[kUpdateInfoEndpointKey] as? String,
|
||||
let updateInfoURL = URL(string: updateInfoURLString) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let request = URLRequest(url: updateInfoURL, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: kTimeoutInterval)
|
||||
let task = URLSession.shared.dataTask(with: request) { data, response, error in
|
||||
if let error = error {
|
||||
DispatchQueue.main.async {
|
||||
forced ?
|
||||
callback(.failure(VersionUpdateApiError.connectionError(message: error.localizedDescription))) :
|
||||
callback(.success(.ignored))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
guard let plist = try PropertyListSerialization.propertyList(from: data ?? Data(), options: [], format: nil) as? [AnyHashable: Any],
|
||||
let remoteVersion = plist[kCFBundleVersionKey] as? String,
|
||||
let infoDict = Bundle.main.infoDictionary
|
||||
else {
|
||||
DispatchQueue.main.async {
|
||||
forced ? callback(.success(.noNeedToUpdate)) : callback(.success(.ignored))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Validate info (e.g. bundle identifier)
|
||||
// TODO: Use HTML to display change log, need a new key like UpdateInfoChangeLogURL for this
|
||||
|
||||
let currentVersion = infoDict[kCFBundleVersionKey as String] as? String ?? ""
|
||||
let result = currentVersion.compare(remoteVersion, options: .numeric, range: nil, locale: nil)
|
||||
|
||||
if result != .orderedAscending {
|
||||
DispatchQueue.main.async {
|
||||
forced ? callback(.success(.noNeedToUpdate)) : callback(.success(.ignored))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
guard let siteInfoURLString = plist[kUpdateInfoSiteKey] as? String,
|
||||
let siteInfoURL = URL(string: siteInfoURLString)
|
||||
else {
|
||||
DispatchQueue.main.async {
|
||||
forced ? callback(.success(.noNeedToUpdate)) : callback(.success(.ignored))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var report = VersionUpdateReport(siteUrl: siteInfoURL)
|
||||
var versionDescription = ""
|
||||
let versionDescriptions = plist["Description"] as? [AnyHashable: Any]
|
||||
if let versionDescriptions = versionDescriptions {
|
||||
var locale = "en"
|
||||
let supportedLocales = ["en", "zh-Hant", "zh-Hans"]
|
||||
let preferredTags = Bundle.preferredLocalizations(from: supportedLocales)
|
||||
if let first = preferredTags.first {
|
||||
locale = first
|
||||
}
|
||||
versionDescription = versionDescriptions[locale] as? String ?? versionDescriptions["en"] as? String ?? ""
|
||||
if !versionDescription.isEmpty {
|
||||
versionDescription = "\n\n" + versionDescription
|
||||
}
|
||||
}
|
||||
report.currentShortVersion = infoDict["CFBundleShortVersionString"] as? String ?? ""
|
||||
report.currentVersion = currentVersion
|
||||
report.remoteShortVersion = plist["CFBundleShortVersionString"] as? String ?? ""
|
||||
report.remoteVersion = remoteVersion
|
||||
report.versionDescription = versionDescription
|
||||
DispatchQueue.main.async {
|
||||
callback(.success(.shouldUpdate(report: report)))
|
||||
}
|
||||
} catch {
|
||||
DispatchQueue.main.async {
|
||||
forced ? callback(.success(.noNeedToUpdate)) : callback(.success(.ignored))
|
||||
}
|
||||
}
|
||||
}
|
||||
task.resume()
|
||||
return task
|
||||
}
|
||||
}
|
||||
|
||||
@objc(AppDelegate)
|
||||
class AppDelegate: NSObject, NSApplicationDelegate, NonModalAlertWindowControllerDelegate {
|
||||
|
||||
@IBOutlet weak var window: NSWindow?
|
||||
|
@ -71,12 +172,12 @@ class AppDelegate: NSObject, NSApplicationDelegate, NonModalAlertWindowControlle
|
|||
preferencesWindowController?.window?.orderFront(self)
|
||||
}
|
||||
|
||||
@objc (checkForUpdate)
|
||||
@objc(checkForUpdate)
|
||||
func checkForUpdate() {
|
||||
checkForUpdate(forced: false)
|
||||
}
|
||||
|
||||
@objc (checkForUpdateForced:)
|
||||
@objc(checkForUpdateForced:)
|
||||
func checkForUpdate(forced: Bool) {
|
||||
|
||||
if checkTask != nil {
|
||||
|
@ -99,112 +200,38 @@ class AppDelegate: NSObject, NSApplicationDelegate, NonModalAlertWindowControlle
|
|||
let nextUpdateDate = Date(timeInterval: kNextCheckInterval, since: Date())
|
||||
UserDefaults.standard.set(nextUpdateDate, forKey: kNextUpdateCheckDateKey)
|
||||
|
||||
guard let infoDict = Bundle.main.infoDictionary,
|
||||
let updateInfoURLString = infoDict[kUpdateInfoEndpointKey] as? String,
|
||||
let updateInfoURL = URL(string: updateInfoURLString) else {
|
||||
return
|
||||
}
|
||||
|
||||
let request = URLRequest(url: updateInfoURL, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: kTimeoutInterval)
|
||||
|
||||
func showNoUpdateAvailableAlert() {
|
||||
NonModalAlertWindowController.shared.show(title: NSLocalizedString("Check for Update Completed", comment: ""), content: NSLocalizedString("You are already using the latest version of McBopomofo.", comment: ""), confirmButtonTitle: NSLocalizedString("OK", comment: ""), cancelButtonTitle: nil, cancelAsDefault: false, delegate: nil)
|
||||
}
|
||||
|
||||
let task = URLSession.shared.dataTask(with: request) { data, response, error in
|
||||
checkTask = VersionUpdateApi.check(forced: forced) { result in
|
||||
defer {
|
||||
self.checkTask = nil
|
||||
}
|
||||
|
||||
if let error = error {
|
||||
if forced {
|
||||
let title = NSLocalizedString("Update Check Failed", comment: "")
|
||||
let content = String(format: NSLocalizedString("There may be no internet connection or the server failed to respond.\n\nError message: %@", comment: ""), error.localizedDescription)
|
||||
let buttonTitle = NSLocalizedString("Dismiss", comment: "")
|
||||
|
||||
DispatchQueue.main.async {
|
||||
NonModalAlertWindowController.shared.show(title: title, content: content, confirmButtonTitle: buttonTitle, cancelButtonTitle: nil, cancelAsDefault: false, delegate: nil)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
guard let plist = try PropertyListSerialization.propertyList(from: data ?? Data(), options: [], format: nil) as? [AnyHashable: Any],
|
||||
let remoteVersion = plist[kCFBundleVersionKey] as? String,
|
||||
let infoDict = Bundle.main.infoDictionary
|
||||
else {
|
||||
if forced {
|
||||
DispatchQueue.main.async {
|
||||
showNoUpdateAvailableAlert()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Validate info (e.g. bundle identifier)
|
||||
// TODO: Use HTML to display change log, need a new key like UpdateInfoChangeLogURL for this
|
||||
|
||||
let currentVersion = infoDict[kCFBundleVersionKey as String] as? String ?? ""
|
||||
let result = currentVersion.compare(remoteVersion, options: .numeric, range: nil, locale: nil)
|
||||
|
||||
if result != .orderedAscending {
|
||||
if forced {
|
||||
DispatchQueue.main.async {
|
||||
showNoUpdateAvailableAlert()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
guard let siteInfoURLString = plist[kUpdateInfoSiteKey] as? String,
|
||||
let siteInfoURL = URL(string: siteInfoURLString) else {
|
||||
if forced {
|
||||
DispatchQueue.main.async {
|
||||
showNoUpdateAvailableAlert()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
self.updateNextStepURL = siteInfoURL
|
||||
|
||||
var versionDescription = ""
|
||||
let versionDescriptions = plist["Description"] as? [AnyHashable: Any]
|
||||
if let versionDescriptions = versionDescriptions {
|
||||
var locale = "en"
|
||||
let supportedLocales = ["en", "zh-Hant", "zh-Hans"]
|
||||
let preferredTags = Bundle.preferredLocalizations(from: supportedLocales)
|
||||
if let first = preferredTags.first {
|
||||
locale = first
|
||||
}
|
||||
versionDescription = versionDescriptions[locale] as? String ?? versionDescriptions["en"] as? String ?? ""
|
||||
if !versionDescription.isEmpty {
|
||||
versionDescription = "\n\n" + versionDescription
|
||||
}
|
||||
}
|
||||
|
||||
switch result {
|
||||
case .success(let apiResult):
|
||||
switch apiResult {
|
||||
case .shouldUpdate(let report):
|
||||
self.updateNextStepURL = report.siteUrl
|
||||
let content = String(format: NSLocalizedString("You're currently using McBopomofo %@ (%@), a new version %@ (%@) is now available. Do you want to visit McBopomofo's website to download the version?%@", comment: ""),
|
||||
infoDict["CFBundleShortVersionString"] as? String ?? "",
|
||||
currentVersion,
|
||||
plist["CFBundleShortVersionString"] as? String ?? "",
|
||||
remoteVersion,
|
||||
versionDescription)
|
||||
DispatchQueue.main.async {
|
||||
report.currentShortVersion,
|
||||
report.currentVersion,
|
||||
report.remoteShortVersion,
|
||||
report.remoteVersion,
|
||||
report.versionDescription)
|
||||
NonModalAlertWindowController.shared.show(title: NSLocalizedString("New Version Available", comment: ""), content: content, confirmButtonTitle: NSLocalizedString("Visit Website", comment: ""), cancelButtonTitle: NSLocalizedString("Not Now", comment: ""), cancelAsDefault: false, delegate: self)
|
||||
case .noNeedToUpdate, .ignored:
|
||||
break
|
||||
}
|
||||
|
||||
} catch {
|
||||
if forced {
|
||||
DispatchQueue.main.async {
|
||||
showNoUpdateAvailableAlert()
|
||||
case .failure(let error):
|
||||
switch error {
|
||||
case VersionUpdateApiError.connectionError(let message):
|
||||
let title = NSLocalizedString("Update Check Failed", comment: "")
|
||||
let content = String(format: NSLocalizedString("There may be no internet connection or the server failed to respond.\n\nError message: %@", comment: ""), message)
|
||||
let buttonTitle = NSLocalizedString("Dismiss", comment: "")
|
||||
NonModalAlertWindowController.shared.show(title: title, content: content, confirmButtonTitle: buttonTitle, cancelButtonTitle: nil, cancelAsDefault: false, delegate: nil)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
checkTask = task
|
||||
task.resume()
|
||||
}
|
||||
|
||||
func nonModalAlertWindowControllerDidConfirm(_ controller: NonModalAlertWindowController) {
|
||||
if let updateNextStepURL = updateNextStepURL {
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
# Custom Phrases or Characters.
|
||||
#
|
||||
# See https://github.com/openvanilla/McBopomofo/wiki/使用手冊#手動加詞 for usage.
|
||||
#
|
||||
# Add your phrases and their respective Bopomofo reading below. Use hyphen ("-")
|
||||
# to connect the Bopomofo syllables.
|
||||
#
|
||||
# 小麥注音 ㄒㄧㄠˇ-ㄇㄞˋ-ㄓㄨˋ-ㄧㄣ
|
||||
#
|
||||
# Any line that starts with "#" is treated as comment.
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
# Custom Exculded Characters or Symbols (for Plain Bopomofo).
|
||||
#
|
||||
# See https://github.com/openvanilla/McBopomofo/wiki/使用手冊#手動刪詞 for usage.
|
||||
#
|
||||
# For example, the two lines below will remove the punctuations 〈 and 《 whenever
|
||||
# you type the character <:
|
||||
#
|
||||
# 〈 _punctuation_Standard_<
|
||||
# 《 _punctuation_Standard_<
|
||||
#
|
||||
# Any line that starts with "#" is treated as comment.
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# Custom Exculded Phrases or Characters.
|
||||
#
|
||||
# See https://github.com/openvanilla/McBopomofo/wiki/使用手冊#手動刪詞 for usage.
|
||||
#
|
||||
# For example, the line below will prevent the phrase "家祠" from showing up anywhere
|
||||
# whenever you type "ㄐㄧㄚ ㄘˊ":
|
||||
#
|
||||
# 家祠 ㄐㄧㄚ-ㄘˊ
|
||||
#
|
||||
# Note that you need to use a hyphen ("-") between Bopomofo syllables.
|
||||
#
|
||||
# Any line that starts with "#" is treated as comment.
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
# Custom Replacements File.
|
||||
#
|
||||
# See https://github.com/openvanilla/McBopomofo/wiki/使用手冊#手動換詞 for usage.
|
||||
#
|
||||
# This is an advanced feature. For example, if you add this line:
|
||||
#
|
||||
# 這個 呢個
|
||||
#
|
||||
# All instances of 這個 will be replaced with 呢個.
|
||||
#
|
||||
# Any line that starts with "#" is treated as comment.
|
||||
|
|
@ -1,14 +1,4 @@
|
|||
//
|
||||
// EmacsKeyHelper.swift
|
||||
//
|
||||
// Copyright (c) 2011 The McBopomofo Project.
|
||||
//
|
||||
// Contributors:
|
||||
// Mengjuei Hsieh (@mjhsieh)
|
||||
// Weizhong Yang (@zonble)
|
||||
//
|
||||
// Based on the Syrup Project and the Formosana Library
|
||||
// by Lukhnos Liu (@lukhnos).
|
||||
// Copyright (c) 2022 and onwards The McBopomofo Authors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person
|
||||
// obtaining a copy of this software and associated documentation
|
||||
|
@ -30,7 +20,6 @@
|
|||
// 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
|
||||
|
||||
|
|
|
@ -1,3 +1,26 @@
|
|||
// Copyright (c) 2022 and onwards The McBopomofo Authors.
|
||||
//
|
||||
// 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:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// 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.
|
||||
|
||||
#ifndef PHRASEREPLACEMENTMAP_H
|
||||
#define PHRASEREPLACEMENTMAP_H
|
||||
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
//
|
||||
// UserOverrideModel.h
|
||||
//
|
||||
// Copyright (c) 2017 The McBopomofo Project.
|
||||
// Copyright (c) 2017 ond onwards The McBopomofo Authors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person
|
||||
// obtaining a copy of this software and associated documentation
|
||||
|
@ -23,7 +20,6 @@
|
|||
// 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.
|
||||
//
|
||||
|
||||
#ifndef USEROVERRIDEMODEL_H
|
||||
#define USEROVERRIDEMODEL_H
|
||||
|
|
|
@ -1,14 +1,4 @@
|
|||
//
|
||||
// InputMethodController.h
|
||||
//
|
||||
// Copyright (c) 2011 The McBopomofo Project.
|
||||
//
|
||||
// Contributors:
|
||||
// Mengjuei Hsieh (@mjhsieh)
|
||||
// Weizhong Yang (@zonble)
|
||||
//
|
||||
// Based on the Syrup Project and the Formosana Library
|
||||
// by Lukhnos Liu (@lukhnos).
|
||||
// Copyright (c) 2011 and onwards The McBopomofo Authors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person
|
||||
// obtaining a copy of this software and associated documentation
|
||||
|
@ -30,7 +20,6 @@
|
|||
// 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/Cocoa.h>
|
||||
#import <InputMethodKit/InputMethodKit.h>
|
||||
|
|
|
@ -1,14 +1,4 @@
|
|||
//
|
||||
// InputMethodController.m
|
||||
//
|
||||
// Copyright (c) 2011 The McBopomofo Project.
|
||||
//
|
||||
// Contributors:
|
||||
// Mengjuei Hsieh (@mjhsieh)
|
||||
// Weizhong Yang (@zonble)
|
||||
//
|
||||
// Based on the Syrup Project and the Formosana Library
|
||||
// by Lukhnos Liu (@lukhnos).
|
||||
// Copyright (c) 2011 and onwards The McBopomofo Authors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person
|
||||
// obtaining a copy of this software and associated documentation
|
||||
|
@ -30,7 +20,6 @@
|
|||
// 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 "InputMethodController.h"
|
||||
#import <fstream>
|
||||
|
@ -55,6 +44,8 @@ using namespace McBopomofo;
|
|||
using namespace OpenVanilla;
|
||||
|
||||
static const NSInteger kMinKeyLabelSize = 10;
|
||||
static const NSInteger kMinMarkRangeLength = 2;
|
||||
static const NSInteger kMaxMarkRangeLength = 6;
|
||||
|
||||
// input modes
|
||||
static NSString *const kBopomofoModeIdentifier = @"org.openvanilla.inputmethod.McBopomofo.Bopomofo";
|
||||
|
@ -326,7 +317,7 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
|
|||
return text;
|
||||
}
|
||||
|
||||
if (Preferences.chineneConversionEngine == 1) {
|
||||
if (Preferences.chineseConversionEngine == 1) {
|
||||
return [VXHanConvert convertToSimplifiedFrom:text];
|
||||
}
|
||||
return [OpenCCBridge convertToSimplified:text];
|
||||
|
@ -1372,7 +1363,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
[client attributesForCharacterIndex:cursor lineHeightRectangle:&lineHeightRect];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
NSLog(@"%@", exception);
|
||||
NSLog(@"lineHeightRectangle %@", exception);
|
||||
}
|
||||
|
||||
if (useVerticalMode) {
|
||||
|
@ -1383,6 +1374,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
}
|
||||
|
||||
gCurrentCandidateController.visible = YES;
|
||||
|
||||
}
|
||||
|
||||
#pragma mark - User phrases
|
||||
|
@ -1420,7 +1412,10 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
size_t begin = min(_builder->markerCursorIndex(), _builder->cursorIndex());
|
||||
size_t end = max(_builder->markerCursorIndex(), _builder->cursorIndex());
|
||||
// A phrase should contian at least two characters.
|
||||
if (end - begin < 2) {
|
||||
if (end - begin < kMinMarkRangeLength) {
|
||||
return @"";
|
||||
}
|
||||
if (end - begin > kMaxMarkRangeLength) {
|
||||
return @"";
|
||||
}
|
||||
|
||||
|
@ -1442,6 +1437,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
{
|
||||
NSString *currentMarkedPhrase = [self _currentMarkedTextAndReadings];
|
||||
if (![currentMarkedPhrase length]) {
|
||||
[self beep];
|
||||
return NO;
|
||||
}
|
||||
|
||||
|
@ -1455,9 +1451,21 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
if (!length) {
|
||||
[self _hideTooltip];
|
||||
}
|
||||
else if (length == 1) {
|
||||
NSString *messsage = [NSString stringWithFormat:NSLocalizedString(@"You are now selecting \"%@\". You can add a phrase with two or more characters.", @""), text];
|
||||
[self _showTooltip:messsage client:client];
|
||||
else if (Preferences.phraseReplacementEnabled) {
|
||||
NSString *message = NSLocalizedString(@"Phrase replacement mode is on. Not suggested to add phrase in the mode.", @"");
|
||||
[self _showTooltip:message client:client];
|
||||
}
|
||||
else if (Preferences.chineseConversionStyle == 1 && Preferences.chineseConversionEnabled) {
|
||||
NSString *message = NSLocalizedString(@"Model based Chinese conversion is on. Not suggested to add phrase in the mode.", @"");
|
||||
[self _showTooltip:message client:client];
|
||||
}
|
||||
else if (length < kMinMarkRangeLength) {
|
||||
NSString *message = [NSString stringWithFormat:NSLocalizedString(@"You are now selecting \"%@\". You can add a phrase with two or more characters.", @""), text];
|
||||
[self _showTooltip:message client:client];
|
||||
}
|
||||
else if (length > kMaxMarkRangeLength) {
|
||||
NSString *message = [NSString stringWithFormat:NSLocalizedString(@"You are now selecting \"%@\". A phrase cannot be longer than 6 characters.", @""), text];
|
||||
[self _showTooltip:message client:client];
|
||||
}
|
||||
else {
|
||||
NSString *messsage = [NSString stringWithFormat:NSLocalizedString(@"You are now selecting \"%@\". Press enter to add a new phrase.", @""), text];
|
||||
|
@ -1518,13 +1526,13 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
{
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wunused-result"
|
||||
[Preferences toogleHalfWidthPunctuationEnabled];
|
||||
[Preferences toggleHalfWidthPunctuationEnabled];
|
||||
#pragma GCC diagnostic pop
|
||||
}
|
||||
|
||||
- (void)togglePhraseReplacementEnabled:(id)sender
|
||||
{
|
||||
BOOL enabled = [Preferences tooglePhraseReplacementEnabled];
|
||||
BOOL enabled = [Preferences togglePhraseReplacementEnabled];
|
||||
McBopomofoLM *lm = [LanguageModelManager languageModelMcBopomofo];
|
||||
lm->setPhraseReplacementEnabled(enabled);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
//
|
||||
// AppDelegate.h
|
||||
//
|
||||
// Copyright (c) 2011-2012 The McBopomofo Project.
|
||||
// Copyright (c) 2012 and onwards The McBopomofo Authors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person
|
||||
// obtaining a copy of this software and associated documentation
|
||||
|
@ -23,7 +20,6 @@
|
|||
// 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/Cocoa.h>
|
||||
#import "ArchiveUtil.h"
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
//
|
||||
// AppDelegate.m
|
||||
//
|
||||
// Copyright (c) 2011-2012 The McBopomofo Project.
|
||||
// Copyright (c) 2012 and onwards The McBopomofo Authors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person
|
||||
// obtaining a copy of this software and associated documentation
|
||||
|
@ -23,7 +20,6 @@
|
|||
// 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 "AppDelegate.h"
|
||||
#import <sys/mount.h>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) 2011-2019 The McBopomofo Project.
|
||||
// Copyright (c) 2019 and onwards The McBopomofo Authors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person
|
||||
// obtaining a copy of this software and associated documentation
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) 2011-2019 The McBopomofo Project.
|
||||
// Copyright (c) 2012 and onwards The McBopomofo Authors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person
|
||||
// obtaining a copy of this software and associated documentation
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
//
|
||||
// main.m
|
||||
//
|
||||
// Copyright (c) 2011-2012 The McBopomofo Project.
|
||||
// Copyright (c) 2012 and onwards The McBopomofo Authors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person
|
||||
// obtaining a copy of this software and associated documentation
|
||||
|
@ -23,7 +20,6 @@
|
|||
// 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/Cocoa.h>
|
||||
|
||||
|
|
|
@ -1,3 +1,26 @@
|
|||
// Copyright (c) 2022 and onwards The McBopomofo Authors.
|
||||
//
|
||||
// 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:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// 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 <Foundation/Foundation.h>
|
||||
#import "UserOverrideModel.h"
|
||||
#import "McBopomofoLM.h"
|
||||
|
|
|
@ -1,3 +1,26 @@
|
|||
// Copyright (c) 2022 and onwards The McBopomofo Authors.
|
||||
//
|
||||
// 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:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// 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 "LanguageModelManager.h"
|
||||
#import <fstream>
|
||||
#import <iostream>
|
||||
|
@ -21,6 +44,13 @@ McBopomofoLM gLanguageModelMcBopomofo;
|
|||
McBopomofoLM gLanguageModelPlainBopomofo;
|
||||
UserOverrideModel gUserOverrideModel(kUserOverrideModelCapacity, kObservedOverrideHalflife);
|
||||
|
||||
NSString *const kUserDataTemplateName = @"template-data";
|
||||
NSString *const kExcludedPhrasesMcBopomofoTemplateName = @"template-exclude-phrases";
|
||||
NSString *const kExcludedPhrasesPlainBopomofoTemplateName = @"template-exclude-phrases-plain-bpmf";
|
||||
NSString *const kPhraseReplacementTemplateName = @"template-phrases-replacement";
|
||||
NSString *const kTemplateExtension = @".txt";
|
||||
|
||||
|
||||
@implementation LanguageModelManager
|
||||
|
||||
static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, McBopomofoLM &lm)
|
||||
|
@ -59,7 +89,7 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, McBopomo
|
|||
}
|
||||
|
||||
NSString *text = [NSString stringWithUTF8String:input.c_str()];
|
||||
if (Preferences.chineneConversionEngine == 1) {
|
||||
if (Preferences.chineseConversionEngine == 1) {
|
||||
text = [VXHanConvert convertToSimplifiedFrom:text];
|
||||
}
|
||||
else {
|
||||
|
@ -97,10 +127,19 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, McBopomo
|
|||
return YES;
|
||||
}
|
||||
|
||||
+ (BOOL)checkIfFileExist:(NSString *)filePath
|
||||
+ (BOOL)ensureFileExists:(NSString *)filePath populateWithTemplate:(NSString *)templateBasename extension:(NSString *)ext
|
||||
{
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
|
||||
BOOL result = [[@"" dataUsingEncoding:NSUTF8StringEncoding] writeToFile:filePath atomically:YES];
|
||||
|
||||
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;
|
||||
|
@ -114,16 +153,16 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, McBopomo
|
|||
if (![self checkIfUserDataFolderExists]) {
|
||||
return NO;
|
||||
}
|
||||
if (![self checkIfFileExist:[self userPhrasesDataPathMcBopomofo]]) {
|
||||
if (![self ensureFileExists:[self userPhrasesDataPathMcBopomofo] populateWithTemplate:kUserDataTemplateName extension:kTemplateExtension]) {
|
||||
return NO;
|
||||
}
|
||||
if (![self checkIfFileExist:[self excludedPhrasesDataPathMcBopomofo]]) {
|
||||
if (![self ensureFileExists:[self excludedPhrasesDataPathMcBopomofo] populateWithTemplate:kExcludedPhrasesMcBopomofoTemplateName extension:kTemplateExtension]) {
|
||||
return NO;
|
||||
}
|
||||
if (![self checkIfFileExist:[self excludedPhrasesDataPathPlainBopomofo]]) {
|
||||
if (![self ensureFileExists:[self excludedPhrasesDataPathPlainBopomofo] populateWithTemplate:kExcludedPhrasesPlainBopomofoTemplateName extension:kTemplateExtension]) {
|
||||
return NO;
|
||||
}
|
||||
if (![self checkIfFileExist:[self phraseReplacementDataPathMcBopomofo]]) {
|
||||
if (![self ensureFileExists:[self phraseReplacementDataPathMcBopomofo] populateWithTemplate:kPhraseReplacementTemplateName extension:kTemplateExtension]) {
|
||||
return NO;
|
||||
}
|
||||
return YES;
|
||||
|
|
|
@ -1,14 +1,4 @@
|
|||
//
|
||||
// NonModalAlertWindowController.swift
|
||||
//
|
||||
// Copyright (c) 2011 The McBopomofo Project.
|
||||
//
|
||||
// Contributors:
|
||||
// Mengjuei Hsieh (@mjhsieh)
|
||||
// Weizhong Yang (@zonble)
|
||||
//
|
||||
// Based on the Syrup Project and the Formosana Library
|
||||
// by Lukhnos Liu (@lukhnos).
|
||||
// Copyright (c) 2022 and onwards The McBopomofo Authors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person
|
||||
// obtaining a copy of this software and associated documentation
|
||||
|
@ -30,7 +20,6 @@
|
|||
// 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
|
||||
|
||||
|
@ -40,7 +29,7 @@ import Cocoa
|
|||
}
|
||||
|
||||
class NonModalAlertWindowController: NSWindowController {
|
||||
@objc (sharedInstance)
|
||||
@objc(sharedInstance)
|
||||
static let shared = NonModalAlertWindowController(windowNibName: "NonModalAlertWindowController")
|
||||
|
||||
@IBOutlet weak var titleTextField: NSTextField!
|
||||
|
|
|
@ -1,14 +1,4 @@
|
|||
//
|
||||
// Preferences.swift
|
||||
//
|
||||
// Copyright (c) 2011 The McBopomofo Project.
|
||||
//
|
||||
// Contributors:
|
||||
// Mengjuei Hsieh (@mjhsieh)
|
||||
// Weizhong Yang (@zonble)
|
||||
//
|
||||
// Based on the Syrup Project and the Formosana Library
|
||||
// by Lukhnos Liu (@lukhnos).
|
||||
// Copyright (c) 2022 and onwards The McBopomofo Authors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person
|
||||
// obtaining a copy of this software and associated documentation
|
||||
|
@ -30,14 +20,13 @@
|
|||
// 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
|
||||
|
||||
private let kKeyboardLayoutPreferenceKey = "KeyboardLayout"
|
||||
private let kBasisKeyboardLayoutPreferenceKey = "BasisKeyboardLayout"; // alphanumeric ("ASCII") input basi
|
||||
private let kFunctionKeyKeyboardLayoutPreferenceKey = "FunctionKeyKeyboardLayout"; // alphanumeric ("ASCII") input basi
|
||||
private let kFunctionKeyKeyboardLayoutOverrideIncludeShiftKey = "FunctionKeyKeyboardLayoutOverrideIncludeShift"; // whether include shif
|
||||
private let kBasisKeyboardLayoutPreferenceKey = "BasisKeyboardLayout" // alphanumeric ("ASCII") input basi
|
||||
private let kFunctionKeyKeyboardLayoutPreferenceKey = "FunctionKeyKeyboardLayout" // alphanumeric ("ASCII") input basi
|
||||
private let kFunctionKeyKeyboardLayoutOverrideIncludeShiftKey = "FunctionKeyKeyboardLayoutOverrideIncludeShift" // whether include shif
|
||||
private let kCandidateListTextSizeKey = "CandidateListTextSize"
|
||||
private let kSelectPhraseAfterCursorAsCandidatePreferenceKey = "SelectPhraseAfterCursorAsCandidate"
|
||||
private let kUseHorizontalCandidateListPreferenceKey = "UseHorizontalCandidateList"
|
||||
|
@ -55,7 +44,6 @@ private let kChineseConversionEngineKey = "ChineseConversionEngine"
|
|||
private let kChineseConversionStyle = "ChineseConversionStyle"
|
||||
|
||||
private let kDefaultCandidateListTextSize: CGFloat = 16
|
||||
private let kMinKeyLabelSize: CGFloat = 10
|
||||
private let kMinCandidateListTextSize: CGFloat = 12
|
||||
private let kMaxCandidateListTextSize: CGFloat = 196
|
||||
|
||||
|
@ -80,7 +68,7 @@ struct UserDefault<Value> {
|
|||
|
||||
var wrappedValue: Value {
|
||||
get {
|
||||
return container.object(forKey: key) as? Value ?? defaultValue
|
||||
container.object(forKey: key) as? Value ?? defaultValue
|
||||
}
|
||||
set {
|
||||
container.set(newValue, forKey: key)
|
||||
|
@ -93,7 +81,8 @@ struct CandidateListTextSize {
|
|||
let key: String
|
||||
let defaultValue: CGFloat = kDefaultCandidateListTextSize
|
||||
lazy var container: UserDefault = {
|
||||
UserDefault(key: key, defaultValue: defaultValue) }()
|
||||
UserDefault(key: key, defaultValue: defaultValue)
|
||||
}()
|
||||
|
||||
var wrappedValue: CGFloat {
|
||||
mutating get {
|
||||
|
@ -122,7 +111,8 @@ struct ComposingBufferSize {
|
|||
let key: String
|
||||
let defaultValue: Int = kDefaultComposingBufferSize
|
||||
lazy var container: UserDefault = {
|
||||
UserDefault(key: key, defaultValue: defaultValue) }()
|
||||
UserDefault(key: key, defaultValue: defaultValue)
|
||||
}()
|
||||
|
||||
var wrappedValue: Int {
|
||||
mutating get {
|
||||
|
@ -205,11 +195,33 @@ struct ComposingBufferSize {
|
|||
// MARK: -
|
||||
|
||||
class Preferences: NSObject {
|
||||
static func reset() {
|
||||
let defaults = UserDefaults.standard
|
||||
defaults.removeObject(forKey: kKeyboardLayoutPreferenceKey)
|
||||
defaults.removeObject(forKey: kBasisKeyboardLayoutPreferenceKey)
|
||||
defaults.removeObject(forKey: kFunctionKeyKeyboardLayoutPreferenceKey)
|
||||
defaults.removeObject(forKey: kFunctionKeyKeyboardLayoutOverrideIncludeShiftKey)
|
||||
defaults.removeObject(forKey: kCandidateListTextSizeKey)
|
||||
defaults.removeObject(forKey: kSelectPhraseAfterCursorAsCandidatePreferenceKey)
|
||||
defaults.removeObject(forKey: kUseHorizontalCandidateListPreferenceKey)
|
||||
defaults.removeObject(forKey: kComposingBufferSizePreferenceKey)
|
||||
defaults.removeObject(forKey: kChooseCandidateUsingSpaceKey)
|
||||
defaults.removeObject(forKey: kChineseConversionEnabledKey)
|
||||
defaults.removeObject(forKey: kHalfWidthPunctuationEnabledKey)
|
||||
defaults.removeObject(forKey: kEscToCleanInputBufferKey)
|
||||
defaults.removeObject(forKey: kCandidateTextFontName)
|
||||
defaults.removeObject(forKey: kCandidateKeyLabelFontName)
|
||||
defaults.removeObject(forKey: kCandidateKeys)
|
||||
defaults.removeObject(forKey: kPhraseReplacementEnabledKey)
|
||||
defaults.removeObject(forKey: kChineseConversionEngineKey)
|
||||
defaults.removeObject(forKey: kChineseConversionStyle)
|
||||
}
|
||||
|
||||
@UserDefault(key: kKeyboardLayoutPreferenceKey, defaultValue: 0)
|
||||
@objc static var keyboardLayout: Int
|
||||
|
||||
@objc static var keyboardLayoutName: String {
|
||||
(KeyboardLayout(rawValue: self.keyboardLayout) ?? KeyboardLayout.standard).name
|
||||
(KeyboardLayout(rawValue: keyboardLayout) ?? KeyboardLayout.standard).name
|
||||
}
|
||||
|
||||
@UserDefault(key: kBasisKeyboardLayoutPreferenceKey, defaultValue: "com.apple.keylayout.US")
|
||||
|
@ -247,9 +259,9 @@ class Preferences: NSObject {
|
|||
@UserDefault(key: kHalfWidthPunctuationEnabledKey, defaultValue: false)
|
||||
@objc static var halfWidthPunctuationEnabled: Bool
|
||||
|
||||
@objc static func toogleHalfWidthPunctuationEnabled() -> Bool {
|
||||
@objc static func toggleHalfWidthPunctuationEnabled() -> Bool {
|
||||
halfWidthPunctuationEnabled = !halfWidthPunctuationEnabled
|
||||
return halfWidthPunctuationEnabled;
|
||||
return halfWidthPunctuationEnabled
|
||||
}
|
||||
|
||||
@UserDefault(key: kEscToCleanInputBufferKey, defaultValue: false)
|
||||
|
@ -326,9 +338,9 @@ class Preferences: NSObject {
|
|||
@UserDefault(key: kPhraseReplacementEnabledKey, defaultValue: false)
|
||||
@objc static var phraseReplacementEnabled: Bool
|
||||
|
||||
@objc static func tooglePhraseReplacementEnabled() -> Bool {
|
||||
@objc static func togglePhraseReplacementEnabled() -> Bool {
|
||||
phraseReplacementEnabled = !phraseReplacementEnabled
|
||||
return phraseReplacementEnabled;
|
||||
return phraseReplacementEnabled
|
||||
}
|
||||
|
||||
/// The conversion engine.
|
||||
|
@ -336,10 +348,10 @@ class Preferences: NSObject {
|
|||
/// - 0: OpenCC
|
||||
/// - 1: VXHanConvert
|
||||
@UserDefault(key: kChineseConversionEngineKey, defaultValue: 0)
|
||||
@objc static var chineneConversionEngine: Int
|
||||
@objc static var chineseConversionEngine: Int
|
||||
|
||||
@objc static var chineneConversionEngineName: String? {
|
||||
return ChineseConversionEngine(rawValue: chineneConversionEngine)?.name
|
||||
@objc static var chineseConversionEngineName: String? {
|
||||
ChineseConversionEngine(rawValue: chineseConversionEngine)?.name
|
||||
}
|
||||
|
||||
/// The conversion style.
|
||||
|
@ -350,7 +362,7 @@ class Preferences: NSObject {
|
|||
@objc static var chineseConversionStyle: Int
|
||||
|
||||
@objc static var chineseConversionStyleName: String? {
|
||||
return ChineseConversionStyle(rawValue: chineseConversionStyle)?.name
|
||||
ChineseConversionStyle(rawValue: chineseConversionStyle)?.name
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,14 +1,4 @@
|
|||
//
|
||||
// PreferencesWindowController.swift
|
||||
//
|
||||
// Copyright (c) 2011 The McBopomofo Project.
|
||||
//
|
||||
// Contributors:
|
||||
// Mengjuei Hsieh (@mjhsieh)
|
||||
// Weizhong Yang (@zonble)
|
||||
//
|
||||
// Based on the Syrup Project and the Formosana Library
|
||||
// by Lukhnos Liu (@lukhnos).
|
||||
// Copyright (c) 2022 and onwards The McBopomofo Authors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person
|
||||
// obtaining a copy of this software and associated documentation
|
||||
|
@ -30,7 +20,6 @@
|
|||
// 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
|
||||
import Carbon
|
||||
|
@ -38,7 +27,7 @@ import Carbon
|
|||
// Please note that the class should be exposed as "PreferencesWindowController"
|
||||
// in Objective-C in order to let IMK to see the same class name as
|
||||
// the "InputMethodServerPreferencesWindowControllerClass" in Info.plist.
|
||||
@objc (PreferencesWindowController) class PreferencesWindowController: NSWindowController {
|
||||
@objc(PreferencesWindowController) class PreferencesWindowController: NSWindowController {
|
||||
@IBOutlet weak var fontSizePopUpButton: NSPopUpButton!
|
||||
@IBOutlet weak var basisKeyboardLayoutButton: NSPopUpButton!
|
||||
@IBOutlet weak var selectionKeyComboBox: NSComboBox!
|
||||
|
@ -104,13 +93,14 @@ import Carbon
|
|||
let icon = IconRef(iconPtr)
|
||||
let image = NSImage(iconRef: icon)
|
||||
|
||||
func resize( _ image: NSImage) -> NSImage {
|
||||
func resize(_ image: NSImage) -> NSImage {
|
||||
let newImage = NSImage(size: NSSize(width: 16, height: 16))
|
||||
newImage.lockFocus()
|
||||
image.draw(in: NSRect(x: 0, y: 0, width: 16, height: 16))
|
||||
newImage.unlockFocus()
|
||||
return newImage
|
||||
}
|
||||
|
||||
menuItem.image = resize(image)
|
||||
}
|
||||
|
||||
|
@ -149,11 +139,9 @@ import Carbon
|
|||
do {
|
||||
try Preferences.validate(candidateKeys: keys)
|
||||
Preferences.candidateKeys = keys
|
||||
}
|
||||
catch Preferences.CandidateKeyError.empty {
|
||||
} catch Preferences.CandidateKeyError.empty {
|
||||
selectionKeyComboBox.stringValue = Preferences.candidateKeys
|
||||
}
|
||||
catch {
|
||||
} catch {
|
||||
if let window = window {
|
||||
let alert = NSAlert(error: error)
|
||||
alert.beginSheetModal(for: window) { response in
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
#!/usr/bin/env python
|
||||
import sys, os
|
||||
import platform
|
||||
|
||||
__author__ = "@zonble and The McBopomofo Authors"
|
||||
__copyright__ = "Copyright 2011 and onwards The McBopomofo Authors"
|
||||
__license__ = "MIT"
|
||||
|
||||
myversion, _, _ = platform.mac_ver()
|
||||
myversion = float('.'.join(myversion.split('.')[:2]))
|
||||
|
||||
|
|
|
@ -69,6 +69,8 @@
|
|||
|
||||
"You are now selecting \"%@\". Press enter to add a new phrase." = "You are now selecting \"%@\". Press enter to add a new phrase.";
|
||||
|
||||
"You are now selecting \"%@\". A phrase cannot be longer than 6 characters." = "You are now selecting \"%@\". A phrase cannot be longer than 6 characters.";
|
||||
|
||||
"Chinese conversion on" = "Chinese conversion on";
|
||||
|
||||
"Chinese conversion off" = "Chinese conversion off";
|
||||
|
@ -89,3 +91,6 @@
|
|||
|
||||
"The length of your candidate keys can not be larger than 15 characters." = "The length of your candidate keys can not be larger than 15 characters.";
|
||||
|
||||
"Phrase replacement mode is on. Not suggested to add phrase in the mode." = "Phrase replacement mode is on. Not suggested to add phrase in the mode.";
|
||||
|
||||
"Model based Chinese conversion is on. Not suggested to add phrase in the mode." = "Model based Chinese conversion is on. Not suggested to add phrase in the mode.";
|
||||
|
|
|
@ -1,14 +1,4 @@
|
|||
//
|
||||
// main.m
|
||||
//
|
||||
// Copyright (c) 2011 The McBopomofo Project.
|
||||
//
|
||||
// Contributors:
|
||||
// Mengjuei Hsieh (@mjhsieh)
|
||||
// Weizhong Yang (@zonble)
|
||||
//
|
||||
// Based on the Syrup Project and the Formosana Library
|
||||
// by Lukhnos Liu (@lukhnos).
|
||||
// Copyright (c) 2011 and onwards The McBopomofo Authors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person
|
||||
// obtaining a copy of this software and associated documentation
|
||||
|
@ -30,7 +20,6 @@
|
|||
// 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/Cocoa.h>
|
||||
#import <InputMethodKit/InputMethodKit.h>
|
||||
|
|
|
@ -69,6 +69,8 @@
|
|||
|
||||
"You are now selecting \"%@\". Press enter to add a new phrase." = "您目前選擇了 \"%@\"。按下 Enter 就可以加入到使用者詞彙中。";
|
||||
|
||||
"You are now selecting \"%@\". A phrase cannot be longer than 6 characters." = "您目前選擇了 \"%@\"。自訂詞彙不能超過六個字元。";
|
||||
|
||||
"Chinese conversion on" = "已經切換到簡體中文模式";
|
||||
|
||||
"Chinese conversion off" = "已經切換到繁體中文模式";
|
||||
|
@ -88,3 +90,7 @@
|
|||
"The length of your candidate keys can not be less than 4 characters." = "選字按鍵數量不可小於 4。";
|
||||
|
||||
"The length of your candidate keys can not be larger than 15 characters." = "選字按鍵數量不可大於 15。";
|
||||
|
||||
"Phrase replacement mode is on. Not suggested to add phrase in the mode." = "詞彙轉換已開啟,不建議在此模式下加詞。";
|
||||
|
||||
"Model based Chinese conversion is on. Not suggested to add phrase in the mode." = "您已開啟將語言模型轉為簡體中文,不建議在此模式下加詞。";
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
# 手動加詞資料檔
|
||||
#
|
||||
# 使用方式請參考 https://github.com/openvanilla/McBopomofo/wiki/使用手冊#手動加詞
|
||||
#
|
||||
# 請在下方加入用戶自訂字詞。每個詞後面要有字詞的讀音。注音音節之間要用減
|
||||
# 號 ("-") 分隔。例如,以下範例加入「小麥注音」一詞:
|
||||
#
|
||||
# 小麥注音 ㄒㄧㄠˇ-ㄇㄞˋ-ㄓㄨˋ-ㄧㄣ
|
||||
#
|
||||
# 如果任何一行以 "#" 開頭,該行將被當作註解忽略。
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
# 手動刪詞資料檔(傳統注音)
|
||||
#
|
||||
# 使用方式請參考 https://github.com/openvanilla/McBopomofo/wiki/使用手冊#手動刪詞
|
||||
#
|
||||
# 以下範例刪除輸入 < 時在選字窗出現的 〈 及 《 兩個標點符號:
|
||||
#
|
||||
# 〈 _punctuation_Standard_<
|
||||
# 《 _punctuation_Standard_<
|
||||
#
|
||||
# 如果任何一行以 "#" 開頭,該行將被當作註解忽略。
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
# 手動刪詞資料檔
|
||||
#
|
||||
# 使用方式請參考 https://github.com/openvanilla/McBopomofo/wiki/使用手冊#手動刪詞
|
||||
#
|
||||
# 如果將下面這行範例加入資料檔中,輸入 "ㄐㄧㄚ ㄘˊ" 時不會再看到「家祠」一詞:
|
||||
#
|
||||
# 家祠 ㄐㄧㄚ-ㄘˊ
|
||||
#
|
||||
# 請注意,注音音節之間要用減號 ("-") 分隔。
|
||||
#
|
||||
# 如果任何一行以 "#" 開頭,該行將被當作註解忽略。
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
# 手動換詞資料檔
|
||||
#
|
||||
# 使用方式請參考 https://github.com/openvanilla/McBopomofo/wiki/使用手冊#手動換詞
|
||||
#
|
||||
#
|
||||
# 這是進階功能。如果加入以下範例的換詞規則,「這個」會被替換成「呢個」:
|
||||
#
|
||||
# 這個 呢個
|
||||
#
|
||||
# 如果任何一行以 "#" 開頭,該行將被當作註解忽略。
|
||||
|
Loading…
Reference in New Issue