From 21264a8a27893a53d1d2cfe631467dc31c16dd10 Mon Sep 17 00:00:00 2001 From: zonble Date: Tue, 18 Jan 2022 15:37:00 +0800 Subject: [PATCH 01/16] Adds unit test cases. --- .../continuous-integration-workflow.yml | 13 +- Packages/CandidateUI/Package.swift | 4 + .../CandidateUI/CandidateController.swift | 6 +- .../VerticalCandidateController.swift | 12 +- .../HorizontalCandidateControllerTests.swift | 113 +++++++++++++++++ .../VerticalCandidateControllerTests.swift | 117 ++++++++++++++++++ 6 files changed, 261 insertions(+), 4 deletions(-) create mode 100644 Packages/CandidateUI/Tests/CandidateUITests/HorizontalCandidateControllerTests.swift create mode 100644 Packages/CandidateUI/Tests/CandidateUITests/VerticalCandidateControllerTests.swift diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index b70b3abe..af0e7d1f 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -17,4 +17,15 @@ jobs: run: xcodebuild -scheme McBopomofo -configuration Release build - name: Build run: xcodebuild -scheme McBopomofoInstaller -configuration Release build - + - name: Test CandidateUI + run: swift test + working-directory: Packages/CandidateUI + - 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 diff --git a/Packages/CandidateUI/Package.swift b/Packages/CandidateUI/Package.swift index 65fad68a..c8267e82 100644 --- a/Packages/CandidateUI/Package.swift +++ b/Packages/CandidateUI/Package.swift @@ -21,5 +21,9 @@ let package = Package( .target( name: "CandidateUI", dependencies: []), + .testTarget( + name: "CandidateUITests", + dependencies: ["CandidateUI"]), + ] ) diff --git a/Packages/CandidateUI/Sources/CandidateUI/CandidateController.swift b/Packages/CandidateUI/Sources/CandidateUI/CandidateController.swift index 073eef4a..c583b9bb 100644 --- a/Packages/CandidateUI/Sources/CandidateUI/CandidateController.swift +++ b/Packages/CandidateUI/Sources/CandidateUI/CandidateController.swift @@ -43,7 +43,11 @@ public protocol CandidateControllerDelegate: AnyObject { @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 { diff --git a/Packages/CandidateUI/Sources/CandidateUI/VerticalCandidateController.swift b/Packages/CandidateUI/Sources/CandidateUI/VerticalCandidateController.swift index 9f01ace7..d12721bf 100644 --- a/Packages/CandidateUI/Sources/CandidateUI/VerticalCandidateController.swift +++ b/Packages/CandidateUI/Sources/CandidateUI/VerticalCandidateController.swift @@ -309,6 +309,10 @@ 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 firstVisibleRow > selectedRow { + return + } keyLabelStripView.highlightedIndex = UInt(selectedRow - firstVisibleRow) keyLabelStripView.setNeedsDisplay(keyLabelStripView.frame) @@ -343,7 +347,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 +375,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 diff --git a/Packages/CandidateUI/Tests/CandidateUITests/HorizontalCandidateControllerTests.swift b/Packages/CandidateUI/Tests/CandidateUITests/HorizontalCandidateControllerTests.swift new file mode 100644 index 00000000..1aa3003d --- /dev/null +++ b/Packages/CandidateUI/Tests/CandidateUITests/HorizontalCandidateControllerTests.swift @@ -0,0 +1,113 @@ +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 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) + } + +} diff --git a/Packages/CandidateUI/Tests/CandidateUITests/VerticalCandidateControllerTests.swift b/Packages/CandidateUI/Tests/CandidateUITests/VerticalCandidateControllerTests.swift new file mode 100644 index 00000000..afc54c41 --- /dev/null +++ b/Packages/CandidateUI/Tests/CandidateUITests/VerticalCandidateControllerTests.swift @@ -0,0 +1,117 @@ +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 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) + } + +} From fb043b9308e5b14ab62c0e3cbda443a58670a2c5 Mon Sep 17 00:00:00 2001 From: zonble Date: Tue, 18 Jan 2022 15:39:07 +0800 Subject: [PATCH 02/16] Updates CI settings. --- .github/workflows/continuous-integration-workflow.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index af0e7d1f..f52cf8cd 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -20,9 +20,6 @@ jobs: - name: Test CandidateUI run: swift test working-directory: Packages/CandidateUI - - name: Test CandidateUI - run: swift test - working-directory: Packages/CandidateUI - name: Test OpenCCBridge run: swift test working-directory: Packages/OpenCCBridge From 29813f93e5b7aa918e1dc091128e3ce6ddda5ae0 Mon Sep 17 00:00:00 2001 From: zonble Date: Tue, 18 Jan 2022 17:05:51 +0800 Subject: [PATCH 03/16] Adds test cases. --- .../continuous-integration-workflow.yml | 4 +- McBopomofo.xcodeproj/project.pbxproj | 162 ++++++++++++++++++ .../xcschemes/McBopomofo.xcscheme | 10 ++ McBopomofoTests/PreferencesTests.swift | 158 +++++++++++++++++ Source/InputMethodController.mm | 4 +- Source/Preferences.swift | 27 ++- 6 files changed, 359 insertions(+), 6 deletions(-) create mode 100644 McBopomofoTests/PreferencesTests.swift diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index f52cf8cd..b73aaa0e 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -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 @@ -17,6 +17,8 @@ jobs: run: xcodebuild -scheme McBopomofo -configuration Release build - name: Build run: xcodebuild -scheme McBopomofoInstaller -configuration Release build + - name: Test + run: xcodebuild -scheme McBopomofo -configuration Debug test - name: Test CandidateUI run: swift test working-directory: Packages/CandidateUI diff --git a/McBopomofo.xcodeproj/project.pbxproj b/McBopomofo.xcodeproj/project.pbxproj index 64f9430c..e4a61870 100644 --- a/McBopomofo.xcodeproj/project.pbxproj +++ b/McBopomofo.xcodeproj/project.pbxproj @@ -55,6 +55,7 @@ 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 */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -72,6 +73,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 */ @@ -191,6 +199,8 @@ D47F7DCF278C0897002F9DD7 /* NonModalAlertWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonModalAlertWindowController.swift; sourceTree = ""; }; D47F7DD1278C1263002F9DD7 /* UserOverrideModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UserOverrideModel.h; sourceTree = ""; }; D47F7DD2278C1263002F9DD7 /* UserOverrideModel.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = UserOverrideModel.cpp; sourceTree = ""; }; + 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 = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -218,6 +228,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + D485D3B32796A8A000657FF3 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -226,6 +243,7 @@ children = ( D427F766278C9CBD004A2160 /* Packages */, 6A0D4EC215FC0D3C00ABF4B3 /* Source */, + D485D3B72796A8A000657FF3 /* McBopomofoTests */, 6A0D4EA515FC0D2D00ABF4B3 /* Frameworks */, 6A0D4EA315FC0D2D00ABF4B3 /* Products */, ); @@ -236,6 +254,7 @@ children = ( 6A0D4EA215FC0D2D00ABF4B3 /* McBopomofo.app */, 6ACA41CB15FC1D7500935EF6 /* McBopomofoInstaller.app */, + D485D3B62796A8A000657FF3 /* McBopomofoTests.xctest */, ); name = Products; sourceTree = ""; @@ -431,6 +450,14 @@ name = Packages; sourceTree = ""; }; + D485D3B72796A8A000657FF3 /* McBopomofoTests */ = { + isa = PBXGroup; + children = ( + D485D3B82796A8A000657FF3 /* PreferencesTests.swift */, + ); + path = McBopomofoTests; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXLegacyTarget section */ @@ -499,17 +526,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 +581,7 @@ 6A0D4EA115FC0D2D00ABF4B3 /* McBopomofo */, 6ACA41CA15FC1D7500935EF6 /* McBopomofoInstaller */, 6A38BC2115FC12FD00A8A51F /* Data */, + D485D3B52796A8A000657FF3 /* McBopomofoTests */, ); }; /* End PBXProject section */ @@ -569,6 +620,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + D485D3B42796A8A000657FF3 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ @@ -625,6 +683,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + D485D3B22796A8A000657FF3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D485D3B92796A8A000657FF3 /* PreferencesTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -638,6 +704,11 @@ target = 6A0D4EA115FC0D2D00ABF4B3 /* McBopomofo */; targetProxy = 6ACA420015FC1DCC00935EF6 /* PBXContainerItemProxy */; }; + D485D3BB2796A8A000657FF3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 6A0D4EA115FC0D2D00ABF4B3 /* McBopomofo */; + targetProxy = D485D3BA2796A8A000657FF3 /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -1044,6 +1115,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 = 12.1; + 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 = 12.1; + 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 +1236,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 */ diff --git a/McBopomofo.xcodeproj/xcshareddata/xcschemes/McBopomofo.xcscheme b/McBopomofo.xcodeproj/xcshareddata/xcschemes/McBopomofo.xcscheme index 3b2c3c69..494e3900 100644 --- a/McBopomofo.xcodeproj/xcshareddata/xcschemes/McBopomofo.xcscheme +++ b/McBopomofo.xcodeproj/xcshareddata/xcschemes/McBopomofo.xcscheme @@ -28,6 +28,16 @@ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> + + + + 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); } diff --git a/Source/Preferences.swift b/Source/Preferences.swift index 538484b8..6c0cc7a5 100644 --- a/Source/Preferences.swift +++ b/Source/Preferences.swift @@ -55,7 +55,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 @@ -205,6 +204,28 @@ 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 @@ -247,7 +268,7 @@ 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; } @@ -326,7 +347,7 @@ 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; } From 8d0db31ed09d27eb386a3f3e9a79b13c90b1de8f Mon Sep 17 00:00:00 2001 From: zonble Date: Tue, 18 Jan 2022 17:10:38 +0800 Subject: [PATCH 04/16] Modifies deploy target of the test bundle. --- McBopomofo.xcodeproj/project.pbxproj | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/McBopomofo.xcodeproj/project.pbxproj b/McBopomofo.xcodeproj/project.pbxproj index e4a61870..948b5557 100644 --- a/McBopomofo.xcodeproj/project.pbxproj +++ b/McBopomofo.xcodeproj/project.pbxproj @@ -826,6 +826,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)", @@ -865,6 +866,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", @@ -1144,7 +1146,7 @@ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GENERATE_INFOPLIST_FILE = YES; - MACOSX_DEPLOYMENT_TARGET = 12.1; + MACOSX_DEPLOYMENT_TARGET = 10.10; MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -1183,7 +1185,7 @@ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GENERATE_INFOPLIST_FILE = YES; - MACOSX_DEPLOYMENT_TARGET = 12.1; + MACOSX_DEPLOYMENT_TARGET = 10.10; MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; From 28a9483de829452c99ebb31cf16a942093c84204 Mon Sep 17 00:00:00 2001 From: zonble Date: Tue, 18 Jan 2022 17:45:48 +0800 Subject: [PATCH 05/16] Adds tests. --- McBopomofoTests/PreferencesTests.swift | 99 +++++++++++++++++++ .../CandidateUI/CandidateController.swift | 2 +- .../HorizontalCandidateControllerTests.swift | 29 ++++++ .../VerticalCandidateControllerTests.swift | 29 ++++++ 4 files changed, 158 insertions(+), 1 deletion(-) diff --git a/McBopomofoTests/PreferencesTests.swift b/McBopomofoTests/PreferencesTests.swift index 905f827c..6c4ebc56 100644 --- a/McBopomofoTests/PreferencesTests.swift +++ b/McBopomofoTests/PreferencesTests.swift @@ -156,3 +156,102 @@ class PreferencesTests: XCTestCase { } } + +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") + } + } +} diff --git a/Packages/CandidateUI/Sources/CandidateUI/CandidateController.swift b/Packages/CandidateUI/Sources/CandidateUI/CandidateController.swift index c583b9bb..cc287ea6 100644 --- a/Packages/CandidateUI/Sources/CandidateUI/CandidateController.swift +++ b/Packages/CandidateUI/Sources/CandidateUI/CandidateController.swift @@ -130,7 +130,7 @@ public class CandidateController: NSWindowController { // bottom beneath the screen? if adjustedPoint.y - windowSize.height < screenFrame.minY { - adjustedPoint.y = windowTopLeftPoint.y + adjustedHeight + windowSize.height + adjustedPoint.y = screenFrame.minY + adjustedHeight + windowSize.height } // top over the screen? diff --git a/Packages/CandidateUI/Tests/CandidateUITests/HorizontalCandidateControllerTests.swift b/Packages/CandidateUI/Tests/CandidateUITests/HorizontalCandidateControllerTests.swift index 1aa3003d..585355aa 100644 --- a/Packages/CandidateUI/Tests/CandidateUITests/HorizontalCandidateControllerTests.swift +++ b/Packages/CandidateUI/Tests/CandidateUITests/HorizontalCandidateControllerTests.swift @@ -19,6 +19,35 @@ class HorizontalCandidateControllerTests: XCTestCase { } } + 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: -100), bottomOutOfScreenAdjustmentHeight: 10) + let exp = expectation(description: "wait") + _ = XCTWaiter.wait(for: [exp], timeout: 0.2) + XCTAssert (controller.window?.frame.minX ?? -1 >= 0) + XCTAssert (controller.window?.frame.minY ?? -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() diff --git a/Packages/CandidateUI/Tests/CandidateUITests/VerticalCandidateControllerTests.swift b/Packages/CandidateUI/Tests/CandidateUITests/VerticalCandidateControllerTests.swift index afc54c41..0f11378c 100644 --- a/Packages/CandidateUI/Tests/CandidateUITests/VerticalCandidateControllerTests.swift +++ b/Packages/CandidateUI/Tests/CandidateUITests/VerticalCandidateControllerTests.swift @@ -19,6 +19,35 @@ class VerticalCandidateControllerTests: XCTestCase { } } + 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: -100), bottomOutOfScreenAdjustmentHeight: 10) + let exp = expectation(description: "wait") + _ = XCTWaiter.wait(for: [exp], timeout: 0.2) + XCTAssert (controller.window?.frame.minX ?? -1 >= 0) + XCTAssert (controller.window?.frame.minY ?? -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() From dd1310d40a05c8f3f8104a0409383d0ce10f9b72 Mon Sep 17 00:00:00 2001 From: zonble Date: Tue, 18 Jan 2022 18:39:21 +0800 Subject: [PATCH 06/16] Makes version update testable. --- .../continuous-integration-workflow.yml | 10 +- McBopomofo.xcodeproj/project.pbxproj | 4 + McBopomofoTests/VersionUpdateTests.swift | 20 ++ Source/AppDelegate.swift | 233 ++++++++++-------- 4 files changed, 165 insertions(+), 102 deletions(-) create mode 100644 McBopomofoTests/VersionUpdateTests.swift diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index b73aaa0e..0ddd33a0 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -9,15 +9,15 @@ jobs: DEVELOPER_DIR: /Applications/Xcode.app/Contents/Developer steps: - uses: actions/checkout@v1 - - name: Clean + - 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 - - name: Test + - name: Test McBopomofo App Bundle run: xcodebuild -scheme McBopomofo -configuration Debug test - name: Test CandidateUI run: swift test diff --git a/McBopomofo.xcodeproj/project.pbxproj b/McBopomofo.xcodeproj/project.pbxproj index 948b5557..006cd3d8 100644 --- a/McBopomofo.xcodeproj/project.pbxproj +++ b/McBopomofo.xcodeproj/project.pbxproj @@ -56,6 +56,7 @@ 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 */ @@ -201,6 +202,7 @@ D47F7DD2278C1263002F9DD7 /* UserOverrideModel.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = UserOverrideModel.cpp; sourceTree = ""; }; 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 = ""; }; + D485D3BF2796CE3200657FF3 /* VersionUpdateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionUpdateTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -454,6 +456,7 @@ isa = PBXGroup; children = ( D485D3B82796A8A000657FF3 /* PreferencesTests.swift */, + D485D3BF2796CE3200657FF3 /* VersionUpdateTests.swift */, ); path = McBopomofoTests; sourceTree = ""; @@ -688,6 +691,7 @@ buildActionMask = 2147483647; files = ( D485D3B92796A8A000657FF3 /* PreferencesTests.swift in Sources */, + D485D3C02796CE3200657FF3 /* VersionUpdateTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/McBopomofoTests/VersionUpdateTests.swift b/McBopomofoTests/VersionUpdateTests.swift new file mode 100644 index 00000000..d2f802f2 --- /dev/null +++ b/McBopomofoTests/VersionUpdateTests.swift @@ -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: 3.0) + } +} + + diff --git a/Source/AppDelegate.swift b/Source/AppDelegate.swift index f8d348fb..6fb8d9ae 100644 --- a/Source/AppDelegate.swift +++ b/Source/AppDelegate.swift @@ -42,6 +42,119 @@ private let kUpdateInfoSiteKey = "UpdateInfoSite" private let kNextCheckInterval: TimeInterval = 86400.0 private let kTimeoutInterval: TimeInterval = 60.0 +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)->()) -> 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 { @@ -99,111 +212,37 @@ 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 - } - } - - 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 { + 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: ""), + 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) { From 1e7d4fe2b00f7255dc12aa959760919a5ceb4fd7 Mon Sep 17 00:00:00 2001 From: zonble Date: Tue, 18 Jan 2022 18:50:35 +0800 Subject: [PATCH 07/16] Makes the timeout for version update check test longer. --- McBopomofoTests/VersionUpdateTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/McBopomofoTests/VersionUpdateTests.swift b/McBopomofoTests/VersionUpdateTests.swift index d2f802f2..1b86f452 100644 --- a/McBopomofoTests/VersionUpdateTests.swift +++ b/McBopomofoTests/VersionUpdateTests.swift @@ -13,7 +13,7 @@ class VersionUpdateApiTests: XCTestCase { XCTFail(error.localizedDescription) } } - self.wait(for: [exp], timeout: 3.0) + self.wait(for: [exp], timeout: 20.0) } } From 0d80d28e0ea740ab8e1ba6f7d15ef99a61d77574 Mon Sep 17 00:00:00 2001 From: zonble Date: Tue, 18 Jan 2022 21:16:07 +0800 Subject: [PATCH 08/16] Formats Swift code. --- McBopomofoTests/PreferencesTests.swift | 6 +-- .../CandidateUI/CandidateController.swift | 6 +-- .../HorizontalCandidateController.swift | 8 ++-- .../VerticalCandidateController.swift | 10 ++--- .../HorizontalCandidateControllerTests.swift | 9 ++-- .../VerticalCandidateControllerTests.swift | 9 ++-- .../InputSourceHelper/InputSourceHelper.swift | 16 +++---- .../Sources/TooltipUI/TooltipController.swift | 4 +- Source/AppDelegate.swift | 43 +++++++++---------- Source/InputMethodController.mm | 2 +- Source/LanguageModelManager.mm | 2 +- Source/NonModalAlertWindowController.swift | 2 +- Source/Preferences.swift | 28 ++++++------ Source/PreferencesWindowController.swift | 11 +++-- 14 files changed, 79 insertions(+), 77 deletions(-) diff --git a/McBopomofoTests/PreferencesTests.swift b/McBopomofoTests/PreferencesTests.swift index 6c4ebc56..5310de85 100644 --- a/McBopomofoTests/PreferencesTests.swift +++ b/McBopomofoTests/PreferencesTests.swift @@ -144,9 +144,9 @@ class PreferencesTests: XCTestCase { } func testChineneConversionEngine() { - XCTAssert(Preferences.chineneConversionEngine == 0) - Preferences.chineneConversionEngine = 1 - XCTAssert(Preferences.chineneConversionEngine == 1) + XCTAssert(Preferences.chineseConversionEngine == 0) + Preferences.chineseConversionEngine = 1 + XCTAssert(Preferences.chineseConversionEngine == 1) } func testChineseConversionStyle() { diff --git a/Packages/CandidateUI/Sources/CandidateUI/CandidateController.swift b/Packages/CandidateUI/Sources/CandidateUI/CandidateController.swift index cc287ea6..0e63fc3c 100644 --- a/Packages/CandidateUI/Sources/CandidateUI/CandidateController.swift +++ b/Packages/CandidateUI/Sources/CandidateUI/CandidateController.swift @@ -34,14 +34,14 @@ 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? { didSet { @@ -99,7 +99,7 @@ public class CandidateController: NSWindowController { UInt.max } - @objc (setWindowTopLeftPoint:bottomOutOfScreenAdjustmentHeight:) + @objc(setWindowTopLeftPoint:bottomOutOfScreenAdjustmentHeight:) public func set(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight height: CGFloat) { DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) { self.doSet(windowTopLeftPoint: windowTopLeftPoint, bottomOutOfScreenAdjustmentHeight: height) diff --git a/Packages/CandidateUI/Sources/CandidateUI/HorizontalCandidateController.swift b/Packages/CandidateUI/Sources/CandidateUI/HorizontalCandidateController.swift index c9cab45f..0239d7f9 100644 --- a/Packages/CandidateUI/Sources/CandidateUI/HorizontalCandidateController.swift +++ b/Packages/CandidateUI/Sources/CandidateUI/HorizontalCandidateController.swift @@ -64,7 +64,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.. 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) diff --git a/Packages/CandidateUI/Sources/CandidateUI/VerticalCandidateController.swift b/Packages/CandidateUI/Sources/CandidateUI/VerticalCandidateController.swift index d12721bf..5be7cf02 100644 --- a/Packages/CandidateUI/Sources/CandidateUI/VerticalCandidateController.swift +++ b/Packages/CandidateUI/Sources/CandidateUI/VerticalCandidateController.swift @@ -91,13 +91,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 diff --git a/Packages/CandidateUI/Tests/CandidateUITests/HorizontalCandidateControllerTests.swift b/Packages/CandidateUI/Tests/CandidateUITests/HorizontalCandidateControllerTests.swift index 585355aa..62a2d816 100644 --- a/Packages/CandidateUI/Tests/CandidateUITests/HorizontalCandidateControllerTests.swift +++ b/Packages/CandidateUI/Tests/CandidateUITests/HorizontalCandidateControllerTests.swift @@ -6,6 +6,7 @@ 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) } @@ -29,8 +30,8 @@ class HorizontalCandidateControllerTests: XCTestCase { controller.set(windowTopLeftPoint: NSPoint(x: -100, y: -100), bottomOutOfScreenAdjustmentHeight: 10) let exp = expectation(description: "wait") _ = XCTWaiter.wait(for: [exp], timeout: 0.2) - XCTAssert (controller.window?.frame.minX ?? -1 >= 0) - XCTAssert (controller.window?.frame.minY ?? -1 >= 0) + XCTAssert(controller.window?.frame.minX ?? -1 >= 0) + XCTAssert(controller.window?.frame.minY ?? -1 >= 0) } func testPositioning2() { @@ -44,8 +45,8 @@ class HorizontalCandidateControllerTests: XCTestCase { 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 ) + XCTAssert(controller.window?.frame.maxX ?? CGFloat.greatestFiniteMagnitude <= screenRect.maxX) + XCTAssert(controller.window?.frame.maxY ?? CGFloat.greatestFiniteMagnitude <= screenRect.maxY) } func testReloadData() { diff --git a/Packages/CandidateUI/Tests/CandidateUITests/VerticalCandidateControllerTests.swift b/Packages/CandidateUI/Tests/CandidateUITests/VerticalCandidateControllerTests.swift index 0f11378c..601da6de 100644 --- a/Packages/CandidateUI/Tests/CandidateUITests/VerticalCandidateControllerTests.swift +++ b/Packages/CandidateUI/Tests/CandidateUITests/VerticalCandidateControllerTests.swift @@ -6,6 +6,7 @@ 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) } @@ -29,8 +30,8 @@ class VerticalCandidateControllerTests: XCTestCase { controller.set(windowTopLeftPoint: NSPoint(x: -100, y: -100), bottomOutOfScreenAdjustmentHeight: 10) let exp = expectation(description: "wait") _ = XCTWaiter.wait(for: [exp], timeout: 0.2) - XCTAssert (controller.window?.frame.minX ?? -1 >= 0) - XCTAssert (controller.window?.frame.minY ?? -1 >= 0) + XCTAssert(controller.window?.frame.minX ?? -1 >= 0) + XCTAssert(controller.window?.frame.minY ?? -1 >= 0) } func testPositioning2() { @@ -44,8 +45,8 @@ class VerticalCandidateControllerTests: XCTestCase { 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 ) + XCTAssert(controller.window?.frame.maxX ?? CGFloat.greatestFiniteMagnitude <= screenRect.maxX) + XCTAssert(controller.window?.frame.maxY ?? CGFloat.greatestFiniteMagnitude <= screenRect.maxY) } func testReloadData() { diff --git a/Packages/InputSourceHelper/Sources/InputSourceHelper/InputSourceHelper.swift b/Packages/InputSourceHelper/Sources/InputSourceHelper/InputSourceHelper.swift index a9fc979b..c53d47fd 100644 --- a/Packages/InputSourceHelper/Sources/InputSourceHelper/InputSourceHelper.swift +++ b/Packages/InputSourceHelper/Sources/InputSourceHelper/InputSourceHelper.swift @@ -46,7 +46,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 +64,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.fromOpaque(valuePts).takeUnretainedValue() @@ -78,13 +78,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 +105,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 +126,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 diff --git a/Packages/TooltipUI/Sources/TooltipUI/TooltipController.swift b/Packages/TooltipUI/Sources/TooltipUI/TooltipController.swift index dbbbeb78..76ea5c40 100644 --- a/Packages/TooltipUI/Sources/TooltipUI/TooltipController.swift +++ b/Packages/TooltipUI/Sources/TooltipUI/TooltipController.swift @@ -1,9 +1,9 @@ import Cocoa public class TooltipController: NSWindowController { - private let backgroundColor = NSColor(calibratedHue: 0.16, saturation: 0.22, brightness: 0.97, alpha: 1.0) + private let backgroundColor = NSColor(calibratedHue: 0.16, saturation: 0.22, brightness: 0.97, alpha: 1.0) private var messageTextField: NSTextField - private var tooltip: String = "" { + private var tooltip: String = "" { didSet { messageTextField.stringValue = tooltip adjustSize() diff --git a/Source/AppDelegate.swift b/Source/AppDelegate.swift index 6fb8d9ae..eea90f07 100644 --- a/Source/AppDelegate.swift +++ b/Source/AppDelegate.swift @@ -51,15 +51,14 @@ struct VersionUpdateReport { var versionDescription: String = "" } -enum VersionUpdateApiResult -{ +enum VersionUpdateApiResult { case shouldUpdate(report: VersionUpdateReport) case noNeedToUpdate case ignored } enum VersionUpdateApiError: Error, LocalizedError { - case connectionError(message:String) + case connectionError(message: String) var errorDescription: String? { switch self { @@ -70,11 +69,11 @@ enum VersionUpdateApiError: Error, LocalizedError { } struct VersionUpdateApi { - static func check(forced: Bool, callback: @escaping (Result)->()) -> URLSessionTask? { + static func check(forced: Bool, callback: @escaping (Result) -> ()) -> URLSessionTask? { guard let infoDict = Bundle.main.infoDictionary, let updateInfoURLString = infoDict[kUpdateInfoEndpointKey] as? String, let updateInfoURL = URL(string: updateInfoURLString) else { - return nil + return nil } let request = URLRequest(url: updateInfoURL, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: kTimeoutInterval) @@ -82,8 +81,8 @@ struct VersionUpdateApi { if let error = error { DispatchQueue.main.async { forced ? - callback(.failure(VersionUpdateApiError.connectionError(message: error.localizedDescription))) : - callback(.success(.ignored)) + callback(.failure(VersionUpdateApiError.connectionError(message: error.localizedDescription))) : + callback(.success(.ignored)) } return } @@ -92,9 +91,9 @@ struct VersionUpdateApi { 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 { + else { DispatchQueue.main.async { - forced ? callback(.success(.noNeedToUpdate)): callback(.success(.ignored)) + forced ? callback(.success(.noNeedToUpdate)) : callback(.success(.ignored)) } return } @@ -107,21 +106,21 @@ struct VersionUpdateApi { if result != .orderedAscending { DispatchQueue.main.async { - forced ? callback(.success(.noNeedToUpdate)): callback(.success(.ignored)) + forced ? callback(.success(.noNeedToUpdate)) : callback(.success(.ignored)) } return } guard let siteInfoURLString = plist[kUpdateInfoSiteKey] as? String, let siteInfoURL = URL(string: siteInfoURLString) - else { + else { DispatchQueue.main.async { - forced ? callback(.success(.noNeedToUpdate)): callback(.success(.ignored)) + forced ? callback(.success(.noNeedToUpdate)) : callback(.success(.ignored)) } return } - var report = VersionUpdateReport(siteUrl:siteInfoURL) + var report = VersionUpdateReport(siteUrl: siteInfoURL) var versionDescription = "" let versionDescriptions = plist["Description"] as? [AnyHashable: Any] if let versionDescriptions = versionDescriptions { @@ -146,7 +145,7 @@ struct VersionUpdateApi { } } catch { DispatchQueue.main.async { - forced ? callback(.success(.noNeedToUpdate)): callback(.success(.ignored)) + forced ? callback(.success(.noNeedToUpdate)) : callback(.success(.ignored)) } } } @@ -155,7 +154,7 @@ struct VersionUpdateApi { } } -@objc (AppDelegate) +@objc(AppDelegate) class AppDelegate: NSObject, NSApplicationDelegate, NonModalAlertWindowControllerDelegate { @IBOutlet weak var window: NSWindow? @@ -184,12 +183,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 { @@ -222,11 +221,11 @@ class AppDelegate: NSObject, NSApplicationDelegate, NonModalAlertWindowControlle 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: ""), - report.currentShortVersion, - report.currentVersion, - report.remoteShortVersion, - report.remoteVersion, - report.versionDescription) + 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 diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 7372b319..9ef30ee8 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -326,7 +326,7 @@ static double FindHighestScore(const vector& nodes, double epsilon) return text; } - if (Preferences.chineneConversionEngine == 1) { + if (Preferences.chineseConversionEngine == 1) { return [VXHanConvert convertToSimplifiedFrom:text]; } return [OpenCCBridge convertToSimplified:text]; diff --git a/Source/LanguageModelManager.mm b/Source/LanguageModelManager.mm index bdf0ac9f..6d9193f5 100644 --- a/Source/LanguageModelManager.mm +++ b/Source/LanguageModelManager.mm @@ -59,7 +59,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 { diff --git a/Source/NonModalAlertWindowController.swift b/Source/NonModalAlertWindowController.swift index 2a227bee..0b67d9c2 100644 --- a/Source/NonModalAlertWindowController.swift +++ b/Source/NonModalAlertWindowController.swift @@ -40,7 +40,7 @@ import Cocoa } class NonModalAlertWindowController: NSWindowController { - @objc (sharedInstance) + @objc(sharedInstance) static let shared = NonModalAlertWindowController(windowNibName: "NonModalAlertWindowController") @IBOutlet weak var titleTextField: NSTextField! diff --git a/Source/Preferences.swift b/Source/Preferences.swift index 6c0cc7a5..d0415873 100644 --- a/Source/Preferences.swift +++ b/Source/Preferences.swift @@ -35,9 +35,9 @@ 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" @@ -79,7 +79,7 @@ struct UserDefault { 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) @@ -92,7 +92,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 { @@ -121,7 +122,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 { @@ -230,7 +232,7 @@ class Preferences: NSObject { @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") @@ -270,7 +272,7 @@ class Preferences: NSObject { @objc static func toggleHalfWidthPunctuationEnabled() -> Bool { halfWidthPunctuationEnabled = !halfWidthPunctuationEnabled - return halfWidthPunctuationEnabled; + return halfWidthPunctuationEnabled } @UserDefault(key: kEscToCleanInputBufferKey, defaultValue: false) @@ -349,7 +351,7 @@ class Preferences: NSObject { @objc static func togglePhraseReplacementEnabled() -> Bool { phraseReplacementEnabled = !phraseReplacementEnabled - return phraseReplacementEnabled; + return phraseReplacementEnabled } /// The conversion engine. @@ -357,10 +359,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. @@ -371,7 +373,7 @@ class Preferences: NSObject { @objc static var chineseConversionStyle: Int @objc static var chineseConversionStyleName: String? { - return ChineseConversionStyle(rawValue: chineseConversionStyle)?.name + ChineseConversionStyle(rawValue: chineseConversionStyle)?.name } } diff --git a/Source/PreferencesWindowController.swift b/Source/PreferencesWindowController.swift index 4d756da8..6a3badb7 100644 --- a/Source/PreferencesWindowController.swift +++ b/Source/PreferencesWindowController.swift @@ -38,7 +38,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 +104,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 +150,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 From aa9c31ffcf2486b40e896878422fa6b5dcde5a8d Mon Sep 17 00:00:00 2001 From: zonble Date: Tue, 18 Jan 2022 22:04:09 +0800 Subject: [PATCH 09/16] Fixes the location of the tooltip. --- .../xcschemes/McBopomofo.xcscheme | 3 +- .../CandidateUI/CandidateController.swift | 12 ++++- .../HorizontalCandidateControllerTests.swift | 3 +- .../VerticalCandidateControllerTests.swift | 3 +- .../Sources/TooltipUI/TooltipController.swift | 45 ++++++++++++++++--- Source/InputMethodController.mm | 3 +- 6 files changed, 57 insertions(+), 12 deletions(-) diff --git a/McBopomofo.xcodeproj/xcshareddata/xcschemes/McBopomofo.xcscheme b/McBopomofo.xcodeproj/xcshareddata/xcschemes/McBopomofo.xcscheme index 494e3900..05268ef2 100644 --- a/McBopomofo.xcodeproj/xcshareddata/xcschemes/McBopomofo.xcscheme +++ b/McBopomofo.xcodeproj/xcshareddata/xcschemes/McBopomofo.xcscheme @@ -26,7 +26,8 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - shouldUseLaunchSchemeArgsEnv = "YES"> + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES"> diff --git a/Packages/CandidateUI/Sources/CandidateUI/CandidateController.swift b/Packages/CandidateUI/Sources/CandidateUI/CandidateController.swift index 0e63fc3c..2d1cbfba 100644 --- a/Packages/CandidateUI/Sources/CandidateUI/CandidateController.swift +++ b/Packages/CandidateUI/Sources/CandidateUI/CandidateController.swift @@ -99,6 +99,16 @@ public class CandidateController: NSWindowController { UInt.max } + /// 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()) { @@ -130,7 +140,7 @@ public class CandidateController: NSWindowController { // bottom beneath the screen? if adjustedPoint.y - windowSize.height < screenFrame.minY { - adjustedPoint.y = screenFrame.minY + adjustedHeight + windowSize.height + adjustedPoint.y = windowTopLeftPoint.y + adjustedHeight + windowSize.height } // top over the screen? diff --git a/Packages/CandidateUI/Tests/CandidateUITests/HorizontalCandidateControllerTests.swift b/Packages/CandidateUI/Tests/CandidateUITests/HorizontalCandidateControllerTests.swift index 62a2d816..efb43533 100644 --- a/Packages/CandidateUI/Tests/CandidateUITests/HorizontalCandidateControllerTests.swift +++ b/Packages/CandidateUI/Tests/CandidateUITests/HorizontalCandidateControllerTests.swift @@ -27,11 +27,10 @@ class HorizontalCandidateControllerTests: XCTestCase { controller.keyLabels = ["1", "2", "3", "4"] controller.reloadData() controller.visible = true - controller.set(windowTopLeftPoint: NSPoint(x: -100, y: -100), bottomOutOfScreenAdjustmentHeight: 10) + 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) - XCTAssert(controller.window?.frame.minY ?? -1 >= 0) } func testPositioning2() { diff --git a/Packages/CandidateUI/Tests/CandidateUITests/VerticalCandidateControllerTests.swift b/Packages/CandidateUI/Tests/CandidateUITests/VerticalCandidateControllerTests.swift index 601da6de..b5b00c6c 100644 --- a/Packages/CandidateUI/Tests/CandidateUITests/VerticalCandidateControllerTests.swift +++ b/Packages/CandidateUI/Tests/CandidateUITests/VerticalCandidateControllerTests.swift @@ -27,11 +27,10 @@ class VerticalCandidateControllerTests: XCTestCase { controller.keyLabels = ["1", "2", "3", "4"] controller.reloadData() controller.visible = true - controller.set(windowTopLeftPoint: NSPoint(x: -100, y: -100), bottomOutOfScreenAdjustmentHeight: 10) + 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) - XCTAssert(controller.window?.frame.minY ?? -1 >= 0) } func testPositioning2() { diff --git a/Packages/TooltipUI/Sources/TooltipUI/TooltipController.swift b/Packages/TooltipUI/Sources/TooltipUI/TooltipController.swift index 76ea5c40..70031d7f 100644 --- a/Packages/TooltipUI/Sources/TooltipUI/TooltipController.swift +++ b/Packages/TooltipUI/Sources/TooltipUI/TooltipController.swift @@ -46,12 +46,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() { diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 9ef30ee8..2c503790 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -1372,7 +1372,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 +1383,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } } gCurrentCandidateController.visible = YES; + } #pragma mark - User phrases From d2c039c42d7a1b082414f939af333b4f8fde2a9b Mon Sep 17 00:00:00 2001 From: zonble Date: Tue, 18 Jan 2022 22:19:25 +0800 Subject: [PATCH 10/16] Notifies the users it is not suggested to add phrase when model is converted. --- Source/InputMethodController.mm | 8 ++++++++ Source/en.lproj/Localizable.strings | 3 +++ Source/zh-Hant.lproj/Localizable.strings | 4 ++++ 3 files changed, 15 insertions(+) diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 2c503790..4a03fc78 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -1456,6 +1456,14 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } if (!length) { [self _hideTooltip]; } + 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 == 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]; diff --git a/Source/en.lproj/Localizable.strings b/Source/en.lproj/Localizable.strings index dade91ce..ad91a3bd 100644 --- a/Source/en.lproj/Localizable.strings +++ b/Source/en.lproj/Localizable.strings @@ -89,3 +89,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."; diff --git a/Source/zh-Hant.lproj/Localizable.strings b/Source/zh-Hant.lproj/Localizable.strings index 12785f77..d9ab108a 100644 --- a/Source/zh-Hant.lproj/Localizable.strings +++ b/Source/zh-Hant.lproj/Localizable.strings @@ -88,3 +88,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." = "您已開啟將語言模型轉為簡體中文,不建議在此模式下加詞。"; From 7e2d3df5ba86939a99d69683951443d74ce08754 Mon Sep 17 00:00:00 2001 From: zonble Date: Wed, 19 Jan 2022 00:49:50 +0800 Subject: [PATCH 11/16] Prevents users from adding custom phrases with more than 6 characters. Also fixes typos. --- McBopomofoTests/PreferencesTests.swift | 2 +- Source/InputMethodController.mm | 18 ++++++++++++++---- Source/en.lproj/Localizable.strings | 2 ++ Source/zh-Hant.lproj/Localizable.strings | 2 ++ 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/McBopomofoTests/PreferencesTests.swift b/McBopomofoTests/PreferencesTests.swift index 5310de85..03e71781 100644 --- a/McBopomofoTests/PreferencesTests.swift +++ b/McBopomofoTests/PreferencesTests.swift @@ -143,7 +143,7 @@ class PreferencesTests: XCTestCase { XCTAssert(Preferences.phraseReplacementEnabled == true) } - func testChineneConversionEngine() { + func testChineseConversionEngine() { XCTAssert(Preferences.chineseConversionEngine == 0) Preferences.chineseConversionEngine = 1 XCTAssert(Preferences.chineseConversionEngine == 1) diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 4a03fc78..cd617948 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -55,6 +55,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"; @@ -1421,7 +1423,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 @""; } @@ -1443,6 +1448,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; } @@ -1464,9 +1470,13 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } 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 == 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 (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]; diff --git a/Source/en.lproj/Localizable.strings b/Source/en.lproj/Localizable.strings index ad91a3bd..ba78261c 100644 --- a/Source/en.lproj/Localizable.strings +++ b/Source/en.lproj/Localizable.strings @@ -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"; diff --git a/Source/zh-Hant.lproj/Localizable.strings b/Source/zh-Hant.lproj/Localizable.strings index d9ab108a..b7c711a0 100644 --- a/Source/zh-Hant.lproj/Localizable.strings +++ b/Source/zh-Hant.lproj/Localizable.strings @@ -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" = "已經切換到繁體中文模式"; From 83162b4d8f83e48d0a7fd82fd2c94bf1b507fc1b Mon Sep 17 00:00:00 2001 From: Lukhnos Liu Date: Tue, 18 Jan 2022 12:00:58 -0800 Subject: [PATCH 12/16] Update README and mention Code of Conduct --- README.markdown | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.markdown b/README.markdown index 40257f1c..6d64a061 100644 --- a/README.markdown +++ b/README.markdown @@ -18,6 +18,12 @@ 要注意的是 macOS 可能會限制同一次 login session 能 kill 同一個輸入法 process 的次數(安裝程式透過 kill input method process 來讓新版的輸入法生效)。如果安裝若干次後,發現程式修改的結果並沒有出現,或甚至輸入法已無法再選用,只要登出目前帳號再重新登入即可。 +## 社群公約 + +歡迎小麥注音用戶回報問題與指教,也歡迎大家參與小麥注音開發。 + +我們採用了 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))。 From 61f55670fb1c62c2ee33b8b5dcdab830e2b7d918 Mon Sep 17 00:00:00 2001 From: Lukhnos Liu Date: Tue, 18 Jan 2022 13:40:38 -0800 Subject: [PATCH 13/16] Write templates the first time custom data is used Now that we allow comments in the custom data files, this change writes localized templates as well as basic instructions. Links to McBopomofo User's Manual are also provided. --- McBopomofo.xcodeproj/project.pbxproj | 64 +++++++++++++++++++ Source/Base.lproj/template-data.txt | 11 ++++ .../template-exclude-phrases-plain-bpmf.txt | 12 ++++ .../Base.lproj/template-exclude-phrases.txt | 13 ++++ .../template-phrases-replacement.txt | 12 ++++ Source/LanguageModelManager.mm | 28 ++++++-- Source/zh-Hant.lproj/template-data.txt | 11 ++++ .../template-exclude-phrases-plain-bpmf.txt | 11 ++++ .../template-exclude-phrases.txt | 12 ++++ .../template-phrases-replacement.txt | 11 ++++ 10 files changed, 179 insertions(+), 6 deletions(-) create mode 100644 Source/Base.lproj/template-data.txt create mode 100644 Source/Base.lproj/template-exclude-phrases-plain-bpmf.txt create mode 100644 Source/Base.lproj/template-exclude-phrases.txt create mode 100644 Source/Base.lproj/template-phrases-replacement.txt create mode 100644 Source/zh-Hant.lproj/template-data.txt create mode 100644 Source/zh-Hant.lproj/template-exclude-phrases-plain-bpmf.txt create mode 100644 Source/zh-Hant.lproj/template-exclude-phrases.txt create mode 100644 Source/zh-Hant.lproj/template-phrases-replacement.txt diff --git a/McBopomofo.xcodeproj/project.pbxproj b/McBopomofo.xcodeproj/project.pbxproj index 64f9430c..38c42854 100644 --- a/McBopomofo.xcodeproj/project.pbxproj +++ b/McBopomofo.xcodeproj/project.pbxproj @@ -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 */; }; @@ -146,6 +150,14 @@ 6A2E40F5253A69DA00D1AE1D /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 6A38BBF615FC117A00A8A51F /* data.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = data.txt; sourceTree = ""; }; 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 = ""; }; + 6A6ED1662797650A0012872E /* Base */ = {isa = PBXFileReference; lastKnownFileType = text; name = Base; path = "Base.lproj/template-data.txt"; sourceTree = ""; }; + 6A6ED1682797650A0012872E /* Base */ = {isa = PBXFileReference; lastKnownFileType = text; name = Base; path = "Base.lproj/template-exclude-phrases-plain-bpmf.txt"; sourceTree = ""; }; + 6A6ED16A2797650A0012872E /* Base */ = {isa = PBXFileReference; lastKnownFileType = text; name = Base; path = "Base.lproj/template-exclude-phrases.txt"; sourceTree = ""; }; + 6A6ED16F279765100012872E /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text; name = "zh-Hant"; path = "zh-Hant.lproj/template-data.txt"; sourceTree = ""; }; + 6A6ED170279765140012872E /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text; name = "zh-Hant"; path = "zh-Hant.lproj/template-exclude-phrases-plain-bpmf.txt"; sourceTree = ""; }; + 6A6ED171279765170012872E /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text; name = "zh-Hant"; path = "zh-Hant.lproj/template-exclude-phrases.txt"; sourceTree = ""; }; + 6A6ED1722797651A0012872E /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text; name = "zh-Hant"; path = "zh-Hant.lproj/template-phrases-replacement.txt"; sourceTree = ""; }; 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; }; @@ -379,6 +391,7 @@ 6A0D4F4715FC0EB900ABF4B3 /* Resources */ = { isa = PBXGroup; children = ( + 6A6ED162279764CD0012872E /* Custom Phrase Templates */, 6AFF97F0253B299E007F1C49 /* NonModalAlertWindowController.xib */, 6A0D4EEE15FC0DA600ABF4B3 /* Images */, 6A0D4EF515FC0DA600ABF4B3 /* McBopomofo-Info.plist */, @@ -399,6 +412,17 @@ path = Data; sourceTree = ""; }; + 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 = ""; + }; 6ACA41E715FC1D9000935EF6 /* Installer */ = { isa = PBXGroup; children = ( @@ -540,15 +564,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 */, @@ -680,6 +708,42 @@ name = MainMenu.xib; sourceTree = ""; }; + 6A6ED1632797650A0012872E /* template-phrases-replacement.txt */ = { + isa = PBXVariantGroup; + children = ( + 6A6ED1642797650A0012872E /* Base */, + 6A6ED1722797651A0012872E /* zh-Hant */, + ); + name = "template-phrases-replacement.txt"; + sourceTree = ""; + }; + 6A6ED1652797650A0012872E /* template-data.txt */ = { + isa = PBXVariantGroup; + children = ( + 6A6ED1662797650A0012872E /* Base */, + 6A6ED16F279765100012872E /* zh-Hant */, + ); + name = "template-data.txt"; + sourceTree = ""; + }; + 6A6ED1672797650A0012872E /* template-exclude-phrases-plain-bpmf.txt */ = { + isa = PBXVariantGroup; + children = ( + 6A6ED1682797650A0012872E /* Base */, + 6A6ED170279765140012872E /* zh-Hant */, + ); + name = "template-exclude-phrases-plain-bpmf.txt"; + sourceTree = ""; + }; + 6A6ED1692797650A0012872E /* template-exclude-phrases.txt */ = { + isa = PBXVariantGroup; + children = ( + 6A6ED16A2797650A0012872E /* Base */, + 6A6ED171279765170012872E /* zh-Hant */, + ); + name = "template-exclude-phrases.txt"; + sourceTree = ""; + }; 6ACA41EA15FC1D9000935EF6 /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( diff --git a/Source/Base.lproj/template-data.txt b/Source/Base.lproj/template-data.txt new file mode 100644 index 00000000..29b95587 --- /dev/null +++ b/Source/Base.lproj/template-data.txt @@ -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. + diff --git a/Source/Base.lproj/template-exclude-phrases-plain-bpmf.txt b/Source/Base.lproj/template-exclude-phrases-plain-bpmf.txt new file mode 100644 index 00000000..d12c9c79 --- /dev/null +++ b/Source/Base.lproj/template-exclude-phrases-plain-bpmf.txt @@ -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. + diff --git a/Source/Base.lproj/template-exclude-phrases.txt b/Source/Base.lproj/template-exclude-phrases.txt new file mode 100644 index 00000000..9ee1e861 --- /dev/null +++ b/Source/Base.lproj/template-exclude-phrases.txt @@ -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. + diff --git a/Source/Base.lproj/template-phrases-replacement.txt b/Source/Base.lproj/template-phrases-replacement.txt new file mode 100644 index 00000000..4d792e3f --- /dev/null +++ b/Source/Base.lproj/template-phrases-replacement.txt @@ -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. + diff --git a/Source/LanguageModelManager.mm b/Source/LanguageModelManager.mm index bdf0ac9f..4ae80481 100644 --- a/Source/LanguageModelManager.mm +++ b/Source/LanguageModelManager.mm @@ -21,6 +21,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) @@ -97,10 +104,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 +130,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; diff --git a/Source/zh-Hant.lproj/template-data.txt b/Source/zh-Hant.lproj/template-data.txt new file mode 100644 index 00000000..6a0d7084 --- /dev/null +++ b/Source/zh-Hant.lproj/template-data.txt @@ -0,0 +1,11 @@ +# 手動加詞資料檔 +# +# 使用方式請參考 https://github.com/openvanilla/McBopomofo/wiki/使用手冊#手動加詞 +# +# 請在下方加入用戶自訂字詞。每個詞後面要有字詞的讀音。注音音節之間要用減 +# 號 ("-") 分隔。例如,以下範例加入「小麥注音」一詞: +# +# 小麥注音 ㄒㄧㄠˇ-ㄇㄞˋ-ㄓㄨˋ-ㄧㄣ +# +# 如果任何一行以 "#" 開頭,該行將被當作註解忽略。 + diff --git a/Source/zh-Hant.lproj/template-exclude-phrases-plain-bpmf.txt b/Source/zh-Hant.lproj/template-exclude-phrases-plain-bpmf.txt new file mode 100644 index 00000000..aeadfdce --- /dev/null +++ b/Source/zh-Hant.lproj/template-exclude-phrases-plain-bpmf.txt @@ -0,0 +1,11 @@ +# 手動刪詞資料檔(傳統注音) +# +# 使用方式請參考 https://github.com/openvanilla/McBopomofo/wiki/使用手冊#手動刪詞 +# +# 以下範例刪除輸入 < 時在選字窗出現的 〈 及 《 兩個標點符號: +# +# 〈 _punctuation_Standard_< +# 《 _punctuation_Standard_< +# +# 如果任何一行以 "#" 開頭,該行將被當作註解忽略。 + diff --git a/Source/zh-Hant.lproj/template-exclude-phrases.txt b/Source/zh-Hant.lproj/template-exclude-phrases.txt new file mode 100644 index 00000000..d1a61c8d --- /dev/null +++ b/Source/zh-Hant.lproj/template-exclude-phrases.txt @@ -0,0 +1,12 @@ +# 手動刪詞資料檔 +# +# 使用方式請參考 https://github.com/openvanilla/McBopomofo/wiki/使用手冊#手動刪詞 +# +# 如果將下面這行範例加入資料檔中,輸入 "ㄐㄧㄚ ㄘˊ" 時不會再看到「家祠」一詞: +# +# 家祠 ㄐㄧㄚ-ㄘˊ +# +# 請注意,注音音節之間要用減號 ("-") 分隔。 +# +# 如果任何一行以 "#" 開頭,該行將被當作註解忽略。 + diff --git a/Source/zh-Hant.lproj/template-phrases-replacement.txt b/Source/zh-Hant.lproj/template-phrases-replacement.txt new file mode 100644 index 00000000..898a9e09 --- /dev/null +++ b/Source/zh-Hant.lproj/template-phrases-replacement.txt @@ -0,0 +1,11 @@ +# 手動換詞資料檔 +# +# 使用方式請參考 https://github.com/openvanilla/McBopomofo/wiki/使用手冊#手動換詞 +# +# +# 這是進階功能。如果加入以下範例的換詞規則,「這個」會被替換成「呢個」: +# +# 這個 呢個 +# +# 如果任何一行以 "#" 開頭,該行將被當作註解忽略。 + From 75f321f08868ff3597948fe6cc514e5ec97dbf57 Mon Sep 17 00:00:00 2001 From: Lukhnos Liu Date: Tue, 18 Jan 2022 14:08:44 -0800 Subject: [PATCH 14/16] Update copyright headers (fixes #213) --- .../CandidateUI/CandidateController.swift | 13 +---------- .../HorizontalCandidateController.swift | 13 +---------- .../VerticalCandidateController.swift | 13 +---------- .../InputSourceHelper/InputSourceHelper.swift | 13 +---------- .../NotifierUI/NotifierController.swift | 23 +++++++++++++++++++ .../Sources/OpenCCBridge/OpenCCBridge.swift | 23 +++++++++++++++++++ .../OpenCCBridgeTests/OpenCCBridgeTests.swift | 23 +++++++++++++++++++ .../Sources/TooltipUI/TooltipController.swift | 23 +++++++++++++++++++ .../Sources/VXHanConvert/VXHanConvert.m | 23 +++++++++++++++++++ .../VXHanConvert/include/VXHanConvert.h | 23 +++++++++++++++++++ .../VXHanConvertTests/VXHanConvertTests.swift | 23 +++++++++++++++++++ Source/AppDelegate.swift | 13 +---------- Source/EmacsKeyHelper.swift | 13 +---------- Source/Engine/PhraseReplacementMap.h | 23 +++++++++++++++++++ Source/Engine/UserOverrideModel.h | 6 +---- Source/InputMethodController.h | 13 +---------- Source/InputMethodController.mm | 13 +---------- Source/Installer/AppDelegate.h | 6 +---- Source/Installer/AppDelegate.m | 6 +---- Source/Installer/ArchiveUtil.h | 2 +- Source/Installer/ArchiveUtil.m | 2 +- Source/Installer/main.m | 6 +---- Source/LanguageModelManager.h | 23 +++++++++++++++++++ Source/LanguageModelManager.mm | 23 +++++++++++++++++++ Source/NonModalAlertWindowController.swift | 13 +---------- Source/Preferences.swift | 13 +---------- Source/PreferencesWindowController.swift | 13 +---------- Source/Tools/genRTF.py | 5 ++++ Source/main.m | 13 +---------- 29 files changed, 253 insertions(+), 166 deletions(-) diff --git a/Packages/CandidateUI/Sources/CandidateUI/CandidateController.swift b/Packages/CandidateUI/Sources/CandidateUI/CandidateController.swift index 073eef4a..8f74da59 100644 --- a/Packages/CandidateUI/Sources/CandidateUI/CandidateController.swift +++ b/Packages/CandidateUI/Sources/CandidateUI/CandidateController.swift @@ -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,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 diff --git a/Packages/CandidateUI/Sources/CandidateUI/HorizontalCandidateController.swift b/Packages/CandidateUI/Sources/CandidateUI/HorizontalCandidateController.swift index c9cab45f..c892a882 100644 --- a/Packages/CandidateUI/Sources/CandidateUI/HorizontalCandidateController.swift +++ b/Packages/CandidateUI/Sources/CandidateUI/HorizontalCandidateController.swift @@ -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 diff --git a/Packages/CandidateUI/Sources/CandidateUI/VerticalCandidateController.swift b/Packages/CandidateUI/Sources/CandidateUI/VerticalCandidateController.swift index 9f01ace7..32360b76 100644 --- a/Packages/CandidateUI/Sources/CandidateUI/VerticalCandidateController.swift +++ b/Packages/CandidateUI/Sources/CandidateUI/VerticalCandidateController.swift @@ -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 diff --git a/Packages/InputSourceHelper/Sources/InputSourceHelper/InputSourceHelper.swift b/Packages/InputSourceHelper/Sources/InputSourceHelper/InputSourceHelper.swift index a9fc979b..4ef8fc26 100644 --- a/Packages/InputSourceHelper/Sources/InputSourceHelper/InputSourceHelper.swift +++ b/Packages/InputSourceHelper/Sources/InputSourceHelper/InputSourceHelper.swift @@ -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 diff --git a/Packages/NotifierUI/Sources/NotifierUI/NotifierController.swift b/Packages/NotifierUI/Sources/NotifierUI/NotifierController.swift index b940f4f9..b5b2e66a 100644 --- a/Packages/NotifierUI/Sources/NotifierUI/NotifierController.swift +++ b/Packages/NotifierUI/Sources/NotifierUI/NotifierController.swift @@ -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 { diff --git a/Packages/OpenCCBridge/Sources/OpenCCBridge/OpenCCBridge.swift b/Packages/OpenCCBridge/Sources/OpenCCBridge/OpenCCBridge.swift index 2dafcd9f..18ea60dd 100644 --- a/Packages/OpenCCBridge/Sources/OpenCCBridge/OpenCCBridge.swift +++ b/Packages/OpenCCBridge/Sources/OpenCCBridge/OpenCCBridge.swift @@ -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 diff --git a/Packages/OpenCCBridge/Tests/OpenCCBridgeTests/OpenCCBridgeTests.swift b/Packages/OpenCCBridge/Tests/OpenCCBridgeTests/OpenCCBridgeTests.swift index e7ef4762..30cac4f0 100644 --- a/Packages/OpenCCBridge/Tests/OpenCCBridgeTests/OpenCCBridgeTests.swift +++ b/Packages/OpenCCBridge/Tests/OpenCCBridgeTests/OpenCCBridgeTests.swift @@ -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 diff --git a/Packages/TooltipUI/Sources/TooltipUI/TooltipController.swift b/Packages/TooltipUI/Sources/TooltipUI/TooltipController.swift index dbbbeb78..dfc48966 100644 --- a/Packages/TooltipUI/Sources/TooltipUI/TooltipController.swift +++ b/Packages/TooltipUI/Sources/TooltipUI/TooltipController.swift @@ -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 { diff --git a/Packages/VXHanConvert/Sources/VXHanConvert/VXHanConvert.m b/Packages/VXHanConvert/Sources/VXHanConvert/VXHanConvert.m index 758b655c..f17fb368 100644 --- a/Packages/VXHanConvert/Sources/VXHanConvert/VXHanConvert.m +++ b/Packages/VXHanConvert/Sources/VXHanConvert/VXHanConvert.m @@ -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; diff --git a/Packages/VXHanConvert/Sources/VXHanConvert/include/VXHanConvert.h b/Packages/VXHanConvert/Sources/VXHanConvert/include/VXHanConvert.h index 0d2264bd..b6fbcd4d 100644 --- a/Packages/VXHanConvert/Sources/VXHanConvert/include/VXHanConvert.h +++ b/Packages/VXHanConvert/Sources/VXHanConvert/include/VXHanConvert.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 NS_ASSUME_NONNULL_BEGIN diff --git a/Packages/VXHanConvert/Tests/VXHanConvertTests/VXHanConvertTests.swift b/Packages/VXHanConvert/Tests/VXHanConvertTests/VXHanConvertTests.swift index 2b6cd404..1847d78f 100644 --- a/Packages/VXHanConvert/Tests/VXHanConvertTests/VXHanConvertTests.swift +++ b/Packages/VXHanConvert/Tests/VXHanConvertTests/VXHanConvertTests.swift @@ -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 diff --git a/Source/AppDelegate.swift b/Source/AppDelegate.swift index f8d348fb..1e9fd3b6 100644 --- a/Source/AppDelegate.swift +++ b/Source/AppDelegate.swift @@ -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 diff --git a/Source/EmacsKeyHelper.swift b/Source/EmacsKeyHelper.swift index 8a28df47..f86b93e2 100644 --- a/Source/EmacsKeyHelper.swift +++ b/Source/EmacsKeyHelper.swift @@ -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 diff --git a/Source/Engine/PhraseReplacementMap.h b/Source/Engine/PhraseReplacementMap.h index d58d7c17..6a53a5bc 100644 --- a/Source/Engine/PhraseReplacementMap.h +++ b/Source/Engine/PhraseReplacementMap.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. + #ifndef PHRASEREPLACEMENTMAP_H #define PHRASEREPLACEMENTMAP_H diff --git a/Source/Engine/UserOverrideModel.h b/Source/Engine/UserOverrideModel.h index 0b981923..a8f66743 100644 --- a/Source/Engine/UserOverrideModel.h +++ b/Source/Engine/UserOverrideModel.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 diff --git a/Source/InputMethodController.h b/Source/InputMethodController.h index c1744637..1519be6f 100644 --- a/Source/InputMethodController.h +++ b/Source/InputMethodController.h @@ -1,15 +1,5 @@ +// Copyright (c) 2011 and onwards The McBopomofo Authors. // -// 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). -// // 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 @@ -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 #import diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index ce635f93..987e2d47 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -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 diff --git a/Source/Installer/AppDelegate.h b/Source/Installer/AppDelegate.h index 32b4936f..714f920e 100644 --- a/Source/Installer/AppDelegate.h +++ b/Source/Installer/AppDelegate.h @@ -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 #import "ArchiveUtil.h" diff --git a/Source/Installer/AppDelegate.m b/Source/Installer/AppDelegate.m index 84dbd224..faf034d0 100644 --- a/Source/Installer/AppDelegate.m +++ b/Source/Installer/AppDelegate.m @@ -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 diff --git a/Source/Installer/ArchiveUtil.h b/Source/Installer/ArchiveUtil.h index aab19c61..9bf1415e 100644 --- a/Source/Installer/ArchiveUtil.h +++ b/Source/Installer/ArchiveUtil.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 diff --git a/Source/Installer/ArchiveUtil.m b/Source/Installer/ArchiveUtil.m index 90d47e35..cf2c9f30 100644 --- a/Source/Installer/ArchiveUtil.m +++ b/Source/Installer/ArchiveUtil.m @@ -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 diff --git a/Source/Installer/main.m b/Source/Installer/main.m index 103bf28f..dcdd7d1e 100644 --- a/Source/Installer/main.m +++ b/Source/Installer/main.m @@ -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 diff --git a/Source/LanguageModelManager.h b/Source/LanguageModelManager.h index ce28eaf5..5323d669 100644 --- a/Source/LanguageModelManager.h +++ b/Source/LanguageModelManager.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 #import "UserOverrideModel.h" #import "McBopomofoLM.h" diff --git a/Source/LanguageModelManager.mm b/Source/LanguageModelManager.mm index bdf0ac9f..11b88152 100644 --- a/Source/LanguageModelManager.mm +++ b/Source/LanguageModelManager.mm @@ -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 #import diff --git a/Source/NonModalAlertWindowController.swift b/Source/NonModalAlertWindowController.swift index 2a227bee..a04bf636 100644 --- a/Source/NonModalAlertWindowController.swift +++ b/Source/NonModalAlertWindowController.swift @@ -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 diff --git a/Source/Preferences.swift b/Source/Preferences.swift index 538484b8..2fa12563 100644 --- a/Source/Preferences.swift +++ b/Source/Preferences.swift @@ -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,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 diff --git a/Source/PreferencesWindowController.swift b/Source/PreferencesWindowController.swift index 4d756da8..c45ce9fe 100644 --- a/Source/PreferencesWindowController.swift +++ b/Source/PreferencesWindowController.swift @@ -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 diff --git a/Source/Tools/genRTF.py b/Source/Tools/genRTF.py index f60f69e4..e97929d1 100755 --- a/Source/Tools/genRTF.py +++ b/Source/Tools/genRTF.py @@ -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])) diff --git a/Source/main.m b/Source/main.m index f65d13a7..18c2297e 100644 --- a/Source/main.m +++ b/Source/main.m @@ -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 #import From 8e66c328d69bdab262bc70b684f24d535b65822b Mon Sep 17 00:00:00 2001 From: Lukhnos Liu Date: Tue, 18 Jan 2022 18:55:37 -0800 Subject: [PATCH 15/16] Make reference to the FAQ --- README.markdown | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.markdown b/README.markdown index 6d64a061..0a50b628 100644 --- a/README.markdown +++ b/README.markdown @@ -22,6 +22,8 @@ 歡迎小麥注音用戶回報問題與指教,也歡迎大家參與小麥注音開發。 +首先,請參考我們在「[常見問題](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/)。 ## 軟體授權 From 95f88ce8028490ea21816f03f84c707fdc978c64 Mon Sep 17 00:00:00 2001 From: zonble Date: Wed, 19 Jan 2022 13:20:27 +0800 Subject: [PATCH 16/16] Fixes an underlying issue in the tableViewSelectionDidChange method. --- .../Sources/CandidateUI/VerticalCandidateController.swift | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Packages/CandidateUI/Sources/CandidateUI/VerticalCandidateController.swift b/Packages/CandidateUI/Sources/CandidateUI/VerticalCandidateController.swift index 5be7cf02..dbbbd55d 100644 --- a/Packages/CandidateUI/Sources/CandidateUI/VerticalCandidateController.swift +++ b/Packages/CandidateUI/Sources/CandidateUI/VerticalCandidateController.swift @@ -310,10 +310,12 @@ extension VerticalCandidateController: NSTableViewDataSource, NSTableViewDelegat // 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 firstVisibleRow > selectedRow { - return + if selectedRow >= firstVisibleRow { + keyLabelStripView.highlightedIndex = UInt(selectedRow - firstVisibleRow) + } else { + keyLabelStripView.highlightedIndex = UInt.max } - keyLabelStripView.highlightedIndex = UInt(selectedRow - firstVisibleRow) + keyLabelStripView.setNeedsDisplay(keyLabelStripView.frame) // fix a subtle OS X "bug" that, since we force the scroller to appear,