diff --git a/Assets.xcassets/Symbols/lock.fill.symbolset/lock.fill.svg b/Assets.xcassets/Symbols/lock.fill.symbolset/lock.fill.svg
index 56b1c665e..fd807ca09 100644
--- a/Assets.xcassets/Symbols/lock.fill.symbolset/lock.fill.svg
+++ b/Assets.xcassets/Symbols/lock.fill.symbolset/lock.fill.svg
@@ -71,8 +71,8 @@ PUBLIC "-//W3C//DTD SVG 1.1//EN"
-
-
+
+
diff --git a/Assets.xcassets/Symbols/lock.vertical.fill.symbolset/lock.vertical.fill.svg b/Assets.xcassets/Symbols/lock.vertical.fill.symbolset/lock.vertical.fill.svg
index fef0094d6..d25c206dd 100644
--- a/Assets.xcassets/Symbols/lock.vertical.fill.symbolset/lock.vertical.fill.svg
+++ b/Assets.xcassets/Symbols/lock.vertical.fill.symbolset/lock.vertical.fill.svg
@@ -71,8 +71,8 @@ PUBLIC "-//W3C//DTD SVG 1.1//EN"
-
-
+
+
diff --git a/Assets.xcassets/Symbols/rectangle.compress.vertical.symbolset/rectangle.compress.vertical.svg b/Assets.xcassets/Symbols/rectangle.compress.vertical.symbolset/rectangle.compress.vertical.svg
index ea10765e2..0ffb00f70 100644
--- a/Assets.xcassets/Symbols/rectangle.compress.vertical.symbolset/rectangle.compress.vertical.svg
+++ b/Assets.xcassets/Symbols/rectangle.compress.vertical.symbolset/rectangle.compress.vertical.svg
@@ -71,8 +71,8 @@ PUBLIC "-//W3C//DTD SVG 1.1//EN"
-
-
+
+
diff --git a/Info.plist b/Info.plist
index c168cbf5f..731795dab 100644
--- a/Info.plist
+++ b/Info.plist
@@ -7,13 +7,15 @@
CFBundleExecutable
${EXECUTABLE_NAME}
CFBundleIconFile
- RimeIcon.icns
+ $(ASSETCATALOG_COMPILER_APPICON_NAME)
CFBundleIdentifier
$(PRODUCT_BUNDLE_IDENTIFIER)
CFBundleInfoDictionaryVersion
6.0
CFBundleName
${PRODUCT_NAME}
+ CFBundleDisplayName
+ ${INFOPLIST_KEY_CFBundleDisplayName}
CFBundlePackageType
APPL
CFBundleSignature
@@ -121,6 +123,8 @@
LSUIElement
1
+ NSSupportsSuddenTermination
+
NSMainNibFile
MainMenu
NSPrincipalClass
diff --git a/InfoPlist.xcstrings b/InfoPlist.xcstrings
index f8d6aa418..85300a162 100644
--- a/InfoPlist.xcstrings
+++ b/InfoPlist.xcstrings
@@ -2,36 +2,36 @@
"sourceLanguage" : "en",
"strings" : {
"CFBundleDisplayName" : {
- "extractionState" : "stale",
+ "extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
- "value" : "Squirrel"
+ "value" : "Squirrel Input Method"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
- "value" : "鼠须管"
+ "value" : "鼠须管输入法"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
- "value" : "鼠鬚管"
+ "value" : "鼠鬚管輸入法"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
- "value" : "鼠鬚筆"
+ "value" : "鼠鬚筆輸入法"
}
}
}
},
"CFBundleName" : {
- "comment" : "Bundle name",
+ "extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -60,7 +60,7 @@
}
},
"im.rime.inputmethod.Squirrel" : {
- "extractionState" : "stale",
+ "extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -89,7 +89,7 @@
}
},
"im.rime.inputmethod.Squirrel.Cant" : {
- "extractionState" : "stale",
+ "extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -118,7 +118,7 @@
}
},
"im.rime.inputmethod.Squirrel.Hans" : {
- "extractionState" : "stale",
+ "extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -147,7 +147,7 @@
}
},
"im.rime.inputmethod.Squirrel.Hant" : {
- "extractionState" : "stale",
+ "extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -176,7 +176,7 @@
}
},
"NSHumanReadableCopyright" : {
- "extractionState" : "stale",
+ "extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -206,4 +206,4 @@
}
},
"version" : "1.0"
-}
\ No newline at end of file
+}
diff --git a/Notifications.xcstrings b/Notifications.xcstrings
new file mode 100644
index 000000000..dc18f0729
--- /dev/null
+++ b/Notifications.xcstrings
@@ -0,0 +1,209 @@
+{
+ "sourceLanguage" : "en",
+ "strings" : {
+ "deploy_failure" : {
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Error occurred. See log file $TMPDIR/rime.squirrel.INFO."
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "有错误!请查看日志 $TMPDIR/rime.squirrel.INFO"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "有錯誤!請查看日誌 $TMPDIR/rime.squirrel.INFO"
+ }
+ },
+ "zh-HK" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "有錯誤!請查看日誌 $TMPDIR/rime.squirrel.INFO"
+ }
+ }
+ }
+ },
+ "deploy_start" : {
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Deploying Rime input method engine…"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "部署输入法引擎…"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "部署輸入法引擎⋯"
+ }
+ },
+ "zh-HK" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "部署輸入法引擎⋯"
+ }
+ }
+ }
+ },
+ "deploy_success" : {
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Squirrel is ready."
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "部署完成。"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "部署完成。"
+ }
+ },
+ "zh-HK" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "部署完成。"
+ }
+ }
+ }
+ },
+ "deploy_update" : {
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Deploying Rime for updates…"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "更新输入法引擎…"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "更新輸入法引擎⋯"
+ }
+ },
+ "zh-HK" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "更新輸入法引擎⋯"
+ }
+ }
+ }
+ },
+ "problematic_launch" : {
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Problematic launch detected!\nSquirrel may be suffering a crash due to improper configurations.\nRevert previous modifications to see if the problem recurs."
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "检测到启动有问题!\n“鼠须管”可能因错误设置而崩溃。\n请尝试撤销之前的修改,然后查看问题是否仍旧存在。"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "啟動時偵測到問題!\n「鼠鬚管」可能因設定不當而崩潰。\n請嘗試回退先前的修改,然後查看問題是否依然存在。"
+ }
+ },
+ "zh-HK" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "啟動時偵測到錯誤!\n「鼠鬚筆」可能由於設定不當而崩潰。\n請嘗試回退先前的改動,然後查看問題是否仍然存在。"
+ }
+ }
+ }
+ },
+ "say_voice" : {
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Alex"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "TingTing"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "MeiJia"
+ }
+ },
+ "zh-HK" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sinji"
+ }
+ }
+ }
+ },
+ "Squirrel" : {
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Squirrel"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "鼠须管"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "鼠鬚管"
+ }
+ },
+ "zh-HK" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "鼠鬚筆"
+ }
+ }
+ }
+ }
+ },
+ "version" : "1.0"
+}
\ No newline at end of file
diff --git a/Sparkle b/Sparkle
index 47d3d90ae..41847a58c 160000
--- a/Sparkle
+++ b/Sparkle
@@ -1 +1 @@
-Subproject commit 47d3d90aee3c52b6f61d04ceae426e607df62347
+Subproject commit 41847a58cdef7506b257591fcca6f9495df591d4
diff --git a/Squirrel.xcodeproj/project.pbxproj b/Squirrel.xcodeproj/project.pbxproj
index 19fb287c4..9e831bebc 100644
--- a/Squirrel.xcodeproj/project.pbxproj
+++ b/Squirrel.xcodeproj/project.pbxproj
@@ -80,7 +80,6 @@
A4B8E1B30F645B870094E08B /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A4B8E1B20F645B870094E08B /* Carbon.framework */; };
E93074B70A5C264700470842 /* InputMethodKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E93074B60A5C264700470842 /* InputMethodKit.framework */; };
F42760AE2C07A2F60050B08A /* InfoPlist.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = F42760AD2C07A2F60050B08A /* InfoPlist.xcstrings */; };
- F42760B02C07A2F60050B08A /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = F42760AF2C07A2F60050B08A /* Localizable.xcstrings */; };
F48CFB6B2B327A2E00DB9CF9 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 447765C725C30E6B002415AF /* Sparkle.framework */; };
F493BF7B2B76F28A008BD7D0 /* UserNotifications.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F493BF7A2B76F27E008BD7D0 /* UserNotifications.framework */; };
F49FEC632B8FA4E0009DDC32 /* EmojiCategoryEN.ocd2 in Copy opencc Files */ = {isa = PBXBuildFile; fileRef = F49FEC412B8FA3FB009DDC32 /* EmojiCategoryEN.ocd2 */; };
@@ -105,6 +104,8 @@
F4EC47A32B323223004862A4 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 29B97324FDCFA39411CA2CEA /* AppKit.framework */; };
F4EC47A42B32322F004862A4 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 29B97325FDCFA39411CA2CEA /* Foundation.framework */; };
F4EC47A82B3233D4004862A4 /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F4EC47A72B3233D0004862A4 /* IOKit.framework */; };
+ F4FDF9262C25653200A8E629 /* Notifications.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = F4FDF9242C25653100A8E629 /* Notifications.xcstrings */; };
+ F4FDF9272C25653200A8E629 /* Tooltips.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = F4FDF9252C25653100A8E629 /* Tooltips.xcstrings */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
@@ -317,7 +318,6 @@
A4B8E1B20F645B870094E08B /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; };
E93074B60A5C264700470842 /* InputMethodKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = InputMethodKit.framework; path = System/Library/Frameworks/InputMethodKit.framework; sourceTree = SDKROOT; };
F42760AD2C07A2F60050B08A /* InfoPlist.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = InfoPlist.xcstrings; sourceTree = ""; };
- F42760AF2C07A2F60050B08A /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; };
F42760B12C07A2F60050B08A /* mul */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; name = mul; path = mul.lproj/MainMenu.xcstrings; sourceTree = ""; };
F493BF7A2B76F27E008BD7D0 /* UserNotifications.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotifications.framework; path = System/Library/Frameworks/UserNotifications.framework; sourceTree = SDKROOT; };
F49FEC412B8FA3FB009DDC32 /* EmojiCategoryEN.ocd2 */ = {isa = PBXFileReference; lastKnownFileType = file; path = EmojiCategoryEN.ocd2; sourceTree = ""; };
@@ -340,6 +340,8 @@
F4EC479F2B323203004862A4 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
F4EC47A12B32320B004862A4 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
F4EC47A72B3233D0004862A4 /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = System/Library/Frameworks/IOKit.framework; sourceTree = SDKROOT; };
+ F4FDF9242C25653100A8E629 /* Notifications.xcstrings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json.xcstrings; path = Notifications.xcstrings; sourceTree = ""; };
+ F4FDF9252C25653100A8E629 /* Tooltips.xcstrings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json.xcstrings; path = Tooltips.xcstrings; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -434,7 +436,8 @@
44986A93184B421700B3278D /* LICENSE.txt */,
44986A94184B421700B3278D /* README.md */,
44F7708E152B3334005CF491 /* dsa_pub.pem */,
- F42760AF2C07A2F60050B08A /* Localizable.xcstrings */,
+ F4FDF9242C25653100A8E629 /* Notifications.xcstrings */,
+ F4FDF9252C25653100A8E629 /* Tooltips.xcstrings */,
8D1107310486CEB800E47090 /* Info.plist */,
F42760AD2C07A2F60050B08A /* InfoPlist.xcstrings */,
A45578F41146A75200592C6E /* MainMenu.xib */,
@@ -599,7 +602,7 @@
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
- LastUpgradeCheck = 1540;
+ LastUpgradeCheck = 1600;
};
buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "Squirrel" */;
compatibilityVersion = "Xcode 15.0";
@@ -630,11 +633,12 @@
files = (
A45578F51146A75200592C6E /* MainMenu.xib in Resources */,
446C01D71F767BD400A6C23E /* Assets.xcassets in Resources */,
+ F4FDF9272C25653200A8E629 /* Tooltips.xcstrings in Resources */,
44986A95184B421700B3278D /* LICENSE.txt in Resources */,
44986A96184B421700B3278D /* README.md in Resources */,
+ F4FDF9262C25653200A8E629 /* Notifications.xcstrings in Resources */,
F42760AE2C07A2F60050B08A /* InfoPlist.xcstrings in Resources */,
44F7708F152B3334005CF491 /* dsa_pub.pem in Resources */,
- F42760B02C07A2F60050B08A /* Localizable.xcstrings in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -677,19 +681,17 @@
CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES;
CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES;
CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES;
- CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
+ CLANG_CXX_LANGUAGE_STANDARD = "c++17";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
- CODE_SIGN_IDENTITY = "Apple Development";
- "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
- CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 0.18.0t;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = "";
+ ENABLE_HARDENED_RUNTIME = YES;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(FRAMEWORK_SEARCH_PATHS_QUOTED_FOR_TARGET_1)",
@@ -725,8 +727,8 @@
OTHER_LDFLAGS = "-lrime.1";
PRODUCT_BUNDLE_IDENTIFIER = im.rime.inputmethod.Squirrel;
PRODUCT_NAME = Squirrel;
- PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = macosx;
+ STRINGS_FILE_OUTPUT_ENCODING = "UTF-8";
SWIFT_EMIT_LOC_STRINGS = YES;
WRAPPER_EXTENSION = app;
};
@@ -739,18 +741,16 @@
CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES;
CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES;
CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES;
- CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
+ CLANG_CXX_LANGUAGE_STANDARD = "c++17";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
- CODE_SIGN_IDENTITY = "Apple Development";
- "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
- CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 0.18.0t;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = "";
+ ENABLE_HARDENED_RUNTIME = YES;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(FRAMEWORK_SEARCH_PATHS_QUOTED_FOR_TARGET_1)",
@@ -785,8 +785,8 @@
OTHER_LDFLAGS = "-lrime.1";
PRODUCT_BUNDLE_IDENTIFIER = im.rime.inputmethod.Squirrel;
PRODUCT_NAME = Squirrel;
- PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = macosx;
+ STRINGS_FILE_OUTPUT_ENCODING = "UTF-8";
SWIFT_EMIT_LOC_STRINGS = YES;
WRAPPER_EXTENSION = app;
};
@@ -795,13 +795,14 @@
C01FCF4F08A954540054247B /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
- ALWAYS_SEARCH_USER_PATHS = NO;
+ ALWAYS_SEARCH_USER_PATHS = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = RimeIcon;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES;
CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES;
CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "c++17";
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_ARC_EXCEPTIONS = YES;
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
@@ -835,7 +836,6 @@
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
- GCC_INPUT_FILETYPE = automatic;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
@@ -866,6 +866,7 @@
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
+ STRINGS_FILE_OUTPUT_ENCODING = "UTF-8";
SYSTEM_HEADER_SEARCH_PATHS = /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/Tk.framework/Headers;
};
name = Debug;
@@ -873,13 +874,14 @@
C01FCF5008A954540054247B /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
- ALWAYS_SEARCH_USER_PATHS = NO;
+ ALWAYS_SEARCH_USER_PATHS = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = RimeIcon;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES;
CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES;
CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "c++17";
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_ARC_EXCEPTIONS = YES;
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
@@ -913,7 +915,6 @@
DEAD_CODE_STRIPPING = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
- GCC_INPUT_FILETYPE = automatic;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
@@ -941,6 +942,7 @@
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
ONLY_ACTIVE_ARCH = NO;
SDKROOT = macosx;
+ STRINGS_FILE_OUTPUT_ENCODING = "UTF-8";
SYSTEM_HEADER_SEARCH_PATHS = /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/Tk.framework/Headers;
};
name = Release;
diff --git a/Squirrel.xcodeproj/xcshareddata/xcschemes/Squirrel.xcscheme b/Squirrel.xcodeproj/xcshareddata/xcschemes/Squirrel.xcscheme
index 36681daf7..40582026c 100644
--- a/Squirrel.xcodeproj/xcshareddata/xcschemes/Squirrel.xcscheme
+++ b/Squirrel.xcodeproj/xcshareddata/xcschemes/Squirrel.xcscheme
@@ -1,6 +1,6 @@
-- (bool)boolValueForKey:(NSString* _Nonnull)key;
-- (int)intValueForKey:(NSString* _Nonnull)key;
-- (double)doubleValueForKey:(NSString* _Nonnull)key;
+- (bool)boolValueForOption:(NSString* _Nonnull)option;
+- (int)intValueForOption:(NSString* _Nonnull)option;
+- (double)doubleValueForOption:(NSString* _Nonnull)option;
@end // SquirrelAppOptions
@@ -46,7 +46,7 @@ __attribute__((objc_direct_members))
@property(nonatomic, strong, readonly, nullable) NSString* schemaId;
@property(nonatomic, strong, nonnull) NSString* colorSpace;
-- (instancetype _Nonnull)initWithArg:(NSString* _Nonnull)arg;
+- (instancetype _Nonnull)initWithType:(NSString* _Nonnull)arg;
- (BOOL)openBaseConfig;
- (BOOL)openWithSchemaId:(NSString* _Nonnull)schemaId
baseConfig:(SquirrelConfig* _Nullable)config;
diff --git a/SquirrelConfig.mm b/SquirrelConfig.mm
index df2ca582a..94d5e55ed 100644
--- a/SquirrelConfig.mm
+++ b/SquirrelConfig.mm
@@ -121,24 +121,24 @@ - (void)updateWithRimeSession:(RimeSessionId)session {
@implementation SquirrelAppOptions
-- (bool)boolValueForKey:(NSString*)key {
- if (NSNumber* value = self[key];
+- (bool)boolValueForOption:(NSString*)option {
+ if (NSNumber* value = self[option];
value != nil && strcmp(value.objCType, @encode(BOOL)) == 0) {
return value.boolValue;
}
return NO;
}
-- (int)intValueForKey:(NSString*)key {
- if (NSNumber* value = self[key];
+- (int)intValueForOption:(NSString*)option {
+ if (NSNumber* value = self[option];
value != nil && strcmp(value.objCType, @encode(int)) == 0) {
return value.intValue;
}
return 0;
}
-- (double)doubleValueForKey:(NSString*)key {
- if (NSNumber* value = self[key];
+- (double)doubleValueForOption:(NSString*)option {
+ if (NSNumber* value = self[option];
value != nil && strcmp(value.objCType, @encode(double)) == 0) {
return value.doubleValue;
}
@@ -192,19 +192,19 @@ - (instancetype)init {
return self;
}
-- (instancetype)initWithArg:(NSString*)arg {
+- (instancetype)initWithType:(NSString*)type {
if (self = [super init]) {
_cache = NSCache.alloc.init;
_colorSpace = NSColorSpace.sRGBColorSpace;
_colorSpaceName = @"sRGB";
- if ([arg isEqualToString:@"squirrel"]) {
+ if ([type isEqualToString:@".squirrel"] || [type isEqualToString:@".base"]) {
[self openBaseConfig];
- } else if ([arg isEqualToString:@"default"]) {
- [self openWithConfigId:arg];
- } else if ([arg isEqualToString:@"user"] || [arg isEqualToString:@"installation"]) {
- [self openUserConfig:arg];
+ } else if ([type isEqualToString:@".default"]) {
+ [self openWithConfigId:@"default"];
+ } else if ([type isEqualToString:@".user"] || [type isEqualToString:@".installation"]) {
+ [self openUserConfig:[type substringFromIndex:1]];
} else {
- [self openWithSchemaId:arg baseConfig:nil];
+ [self openWithSchemaId:type baseConfig:[SquirrelConfig.alloc initWithType:@".base"]];
}
}
return self;
@@ -222,11 +222,7 @@ - (BOOL)openWithSchemaId:(NSString*)schemaId
_isOpen = rime_get_api_stdbool()->schema_open(schemaId.UTF8String, &_config);
if (_isOpen) {
_schemaId = schemaId;
- if (baseConfig == nil) {
- _baseConfig = [SquirrelConfig.alloc initWithArg:@"squirrel"];
- } else {
- _baseConfig = baseConfig;
- }
+ _baseConfig = baseConfig;
}
return _isOpen;
}
@@ -245,10 +241,10 @@ - (BOOL)openWithConfigId:(NSString*)configId {
- (void)close {
if (_isOpen && rime_get_api_stdbool()->config_close(&_config)) {
- _baseConfig = nil;
- _schemaId = nil;
_isOpen = NO;
}
+ _baseConfig = nil;
+ _schemaId = nil;
}
- (void)dealloc {
@@ -572,13 +568,14 @@ - (SquirrelOptionSwitcher*)optionSwitcherForSchema {
}
- (SquirrelAppOptions*)appOptionsForApp:(NSString*)bundleId {
- if (SquirrelAppOptions* cachedValue = [self cachedValueOfClass:SquirrelAppOptions.class forKey:bundleId]) {
+ NSString* rootKey = [@"app_options/" append:bundleId];
+ if (SquirrelAppOptions* cachedValue = [self cachedValueOfClass:SquirrelAppOptions.class forKey:rootKey]) {
return cachedValue;
}
- NSString* rootKey = [@"app_options/" append:bundleId];
NSMutableDictionary* appOptions = NSMutableDictionary.alloc.init;
RimeConfigIterator iterator;
if (!rime_get_api_stdbool()->config_begin_map(&iterator, &_config, rootKey.UTF8String)) {
+ [_cache setObject:appOptions forKey:rootKey];
return appOptions.copy;
}
while (rime_get_api_stdbool()->config_next(&iterator)) {
@@ -590,7 +587,7 @@ - (SquirrelAppOptions*)appOptionsForApp:(NSString*)bundleId {
}
}
rime_get_api_stdbool()->config_end(&iterator);
- [_cache setObject:appOptions forKey:bundleId];
+ [_cache setObject:appOptions forKey:rootKey];
return appOptions.copy;
}
diff --git a/SquirrelInputController.hh b/SquirrelInputController.hh
index 13c19966a..67959e73e 100644
--- a/SquirrelInputController.hh
+++ b/SquirrelInputController.hh
@@ -32,6 +32,7 @@ typedef NS_ENUM(NSUInteger, SquirrelIndex) {
};
@property(nonatomic, readonly, weak, nullable, direct, class) SquirrelInputController* currentController;
+@property(nonatomic, direct, class) NSTimeInterval chordDuration;
@property(nonatomic, readonly, strong, nonnull) NSAppearance* viewEffectiveAppearance API_AVAILABLE(macos(10.14));
@property(nonatomic, readonly, strong, nonnull, direct) NSMutableArray* candidateTexts;
@property(nonatomic, readonly, strong, nonnull, direct) NSMutableArray* candidateComments;
diff --git a/SquirrelInputController.mm b/SquirrelInputController.mm
index 891a9601e..2ec88ae7a 100644
--- a/SquirrelInputController.mm
+++ b/SquirrelInputController.mm
@@ -12,6 +12,7 @@
static NSString* const kFullWidthSpace = @" ";
static const int N_KEY_ROLL_OVER = 50;
+static const NSTimeInterval kStatusDelay = 0.2;
@implementation SquirrelInputController {
NSMutableAttributedString* _inlineString;
@@ -30,14 +31,15 @@ @implementation SquirrelInputController {
BOOL _inlineCandidate;
BOOL _goodOldCapsLock;
BOOL _showingSwitcherMenu;
+ BOOL _showingInitialStatus;
// app-specific options and bug fix
SquirrelAppOptions* _appOptions;
BOOL _inlinePlaceholder;
BOOL _panellessCommitFix;
- int _inlineOffset;
+ double _inlineOffset;
// for chord-typing
NSTimer* _chordTimer;
- NSTimeInterval _chordDuration;
+
int _chordKeyCodes[N_KEY_ROLL_OVER];
int _chordModifiers[N_KEY_ROLL_OVER];
int _chordKeyCount;
@@ -45,6 +47,7 @@ @implementation SquirrelInputController {
static SquirrelInputController* __weak _currentController = nil;
static NSString* _currentApp;
+static NSTimeInterval _chordDuration = 0.1;
static int _asciiMode = -1;
+ (void)setCurrentController:(SquirrelInputController*)controller {
@@ -56,6 +59,14 @@ + (SquirrelInputController*)currentController {
return _currentController;
}
++ (void)setChordDuration:(NSTimeInterval)chordDuration {
+ _chordDuration = chordDuration;
+}
+
++ (NSTimeInterval)chordDuration {
+ return _chordDuration;
+}
+
- (NSAppearance*)viewEffectiveAppearance API_AVAILABLE(macos(10.14)) {
return [self.client performSelector:
@selector(viewEffectiveAppearance)] ? : NSApp.effectiveAppearance;
@@ -65,17 +76,13 @@ - (NSAppearance*)viewEffectiveAppearance API_AVAILABLE(macos(10.14)) {
return [NSSet setWithObjects:@"client.viewEffectiveAppearance", nil];
}
-/*!
- @method
- @abstract Receive incoming event
- @discussion This method receives key events from the client application.
- */
+/** - Receive incoming event:
+ - Return `YES` to indicate the the key input was received and dealt with.
+ Key processing will not continue in that case. In other words,
+ the system will not deliver a key-down event to the application.
+ - Returning `NO` means the original key down will be passed on to the client. */
- (BOOL)handleEvent:(NSEvent*)event
client:(id)sender {
- // Return YES to indicate the the key input was received and dealt with.
- // Key processing will not continue in that case. In other words the
- // system will not deliver a key down event to the application.
- // Returning NO means the original key down will be passed on to the client.
BOOL handled = NO;
@autoreleasepool {
@@ -211,12 +218,12 @@ - (BOOL)mouseDownOnCharacterIndex:(NSUInteger)index
lineHeightRectangle:NULL][@"IMKBaseline"] pointValue];
NSPoint tail = [[sender attributesForCharacterIndex:markedRange.length - 1
lineHeightRectangle:NULL][@"IMKBaseline"] pointValue];
- if (point.x > tail.x || index >= markedRange.length) {
+ if (point.x > nexttoward(tail.x, INFINITY) || index >= markedRange.length) {
if (_inlineCandidate && !_inlinePreedit) {
return NO;
}
[self performAction:kPROCESS onIndex:kEndKey];
- } else if (point.x < head.x || index <= 0) {
+ } else if (point.x < nexttoward(head.x, -INFINITY) || index <= 0) {
[self performAction:kPROCESS onIndex:kHomeKey];
} else {
[self moveCursor:_inlineCaretPos
@@ -403,18 +410,18 @@ - (void)performAction:(SquirrelAction)action
- (void)onChordTimer:(NSTimer*)timer {
// chord release triggered by timer
- int processed_keys = 0;
+ int processedKeyCount = 0;
if (_chordKeyCount > 0 && _session != 0) {
// simulate key-ups
for (int i = 0; i < _chordKeyCount; ++i) {
if (rime_get_api_stdbool()->process_key(_session, _chordKeyCodes[i],
- (_chordModifiers[i] | kReleaseMask))) {
- ++processed_keys;
+ _chordModifiers[i] | kReleaseMask)) {
+ ++processedKeyCount;
}
}
}
[self clearChord];
- if (processed_keys > 0) {
+ if (processedKeyCount > 0) {
[self rimeUpdate];
}
}
@@ -474,8 +481,7 @@ - (NSUInteger)recognizedEvents:(id)sender {
- (void)showInitialStatus __attribute__((objc_direct)) {
RIME_STRUCT(RimeStatus_stdbool, status);
if (_session != 0 && rime_get_api_stdbool()->get_status(_session, &status)) {
- _schemaId = @(status.schema_id);
- NSString* schemaName = status.schema_name ? @(status.schema_name) : @(status.schema_id);
+ NSString* schemaName = @(status.schema_name ? : status.schema_id);
NSMutableArray* options = [NSMutableArray.alloc initWithCapacity:3];
if (NSString* asciiMode = getOptionLabel(_session, "ascii_mode", status.is_ascii_mode)) {
[options addObject:asciiMode];
@@ -488,12 +494,14 @@ - (void)showInitialStatus __attribute__((objc_direct)) {
}
rime_get_api_stdbool()->free_status(&status);
NSString* foldedOptions = options.count == 0 ? schemaName :
- [NSString stringWithFormat:@"%@|%@", schemaName, [options componentsJoinedByString:@" "]];
+ [NSString stringWithFormat:@"%@ │ %@", schemaName, [options componentsJoinedByString:@" "]];
[NSApp.squirrelAppDelegate.panel updateStatusLong:foldedOptions statusShort:schemaName];
if (@available(macOS 14.0, *)) {
- _lastModifiers |= NSEventModifierFlagHelp;
+ _showingInitialStatus = YES;
}
- [self rimeUpdate];
+ [NSTimer scheduledTimerWithTimeInterval:kStatusDelay repeats:NO block:^(NSTimer * _Nonnull timer) {
+ [self rimeUpdate];
+ }];
}
}
@@ -505,7 +513,7 @@ - (void)activateServer:(id)sender {
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial
context:nil];
- SquirrelConfig* baseConfig = [SquirrelConfig.alloc initWithArg:@"squirrel"];
+ SquirrelConfig* baseConfig = [SquirrelConfig.alloc initWithType:@".base"];
NSString* keyboardLayout = [baseConfig stringForOption:@"keyboard_layout"];
if ([@"last" caseInsensitiveCompare:keyboardLayout] == NSOrderedSame ||
[keyboardLayout isEqualToString:@""]) {
@@ -520,7 +528,7 @@ - (void)activateServer:(id)sender {
}
[baseConfig close];
- SquirrelConfig* defaultConfig = [SquirrelConfig.alloc initWithArg:@"default"];
+ SquirrelConfig* defaultConfig = [SquirrelConfig.alloc initWithType:@".default"];
if ([defaultConfig hasSection:@"ascii_composer"]) {
_goodOldCapsLock = [defaultConfig boolValueForOption:
@"ascii_composer/good_old_caps_lock"];
@@ -646,7 +654,6 @@ - (void)hidePalettes {
- (void)dealloc {
// NSLog(@"dealloc");
[self destroySession];
- [self clearBuffer];
}
- (NSRange)selectionRange {
@@ -718,11 +725,18 @@ - (void)showInlineString:(NSString*)inlineString
[self updateComposition];
}
-- (CGRect)getIbeamRect __attribute__((objc_direct)) {
+NS_INLINE NSRect NSMakeRect(NSPoint origin, NSSize size) {
+ NSRect r;
+ r.origin = origin;
+ r.size = size;
+ return r;
+}
+
+- (NSRect)getIbeamRect __attribute__((objc_direct)) {
NSRect IbeamRect = NSZeroRect;
[self.client attributesForCharacterIndex:0
lineHeightRectangle:&IbeamRect];
- if (NSEqualRects(IbeamRect, NSZeroRect) && _inlineString.length == 0) {
+ if (NSIsEmptyRect(IbeamRect) && _inlineString.length == 0) {
if (self.client.selectedRange.length == 0) {
// activate inline session, in e.g. table cells, by fake inputs
[self.client setMarkedText:@" "
@@ -739,38 +753,39 @@ - (CGRect)getIbeamRect __attribute__((objc_direct)) {
}
}
if (NSIsEmptyRect(IbeamRect)) {
- return IbeamRect;
+ return NSMakeRect(NSEvent.mouseLocation, NSZeroSize);
+ }
+ BOOL sweepVertical = NSWidth(IbeamRect) > NSHeight(IbeamRect);
+ if (isnormal(_inlineOffset)) {
+ IbeamRect = NSOffsetRect(IbeamRect, sweepVertical ? _inlineOffset : 0.0, sweepVertical ? 0.0 : _inlineOffset);
}
- NSWidth(IbeamRect) > NSHeight(IbeamRect) ? IbeamRect.origin.x += _inlineOffset
- : IbeamRect.origin.y += _inlineOffset;
- if (@available(macOS 14.0, *)) { // avoid overlapping with cursor effects view
+ if (@available(macOS 14.0, *)) {
+ // avoid overlapping with cursor effects view
if ((_goodOldCapsLock && (_lastModifiers & NSEventModifierFlagCapsLock) != 0) ||
(_lastModifiers & NSEventModifierFlagHelp) != 0) {
- _lastModifiers &= ~NSEventModifierFlagHelp;
- NSRect screenRect = NSScreen.mainScreen.frame;
- if (NSIntersectsRect(IbeamRect, screenRect)) {
- screenRect = NSScreen.mainScreen.visibleFrame;
- if (NSWidth(IbeamRect) > NSHeight(IbeamRect)) {
- NSRect capslockAccessory = NSMakeRect(NSMinX(IbeamRect) - 30, NSMinY(IbeamRect),
- 27, NSHeight(IbeamRect));
- if (NSMinX(capslockAccessory) < NSMinX(screenRect)) {
- capslockAccessory.origin.x = NSMinX(screenRect);
- }
- if (NSMaxX(capslockAccessory) > NSMaxX(screenRect)) {
- capslockAccessory.origin.x = NSMaxX(screenRect) - NSWidth(capslockAccessory);
- }
- IbeamRect = NSUnionRect(IbeamRect, capslockAccessory);
- } else {
- NSRect capslockAccessory = NSMakeRect(NSMinX(IbeamRect), NSMinY(IbeamRect) - 26,
- NSWidth(IbeamRect), 23);
- if (NSMinY(capslockAccessory) < NSMinY(screenRect)) {
- capslockAccessory.origin.y = NSMaxY(screenRect) + 3;
- }
- if (NSMaxY(capslockAccessory) > NSMaxY(screenRect)) {
- capslockAccessory.origin.y = NSMaxY(screenRect) - NSHeight(capslockAccessory);
- }
- IbeamRect = NSUnionRect(IbeamRect, capslockAccessory);
+ if (_showingInitialStatus)
+ _showingInitialStatus = NO;
+ NSRect screenRect = NSScreen.mainScreen.visibleFrame;
+ if (sweepVertical) {
+ NSRect capslockAccessory = NSMakeRect(NSMinX(IbeamRect) - 30, NSMinY(IbeamRect),
+ 27, NSHeight(IbeamRect));
+ if (NSMinX(capslockAccessory) < nexttoward(NSMinX(screenRect), INFINITY)) {
+ capslockAccessory.origin.x = NSMinX(screenRect);
+ }
+ if (NSMaxX(capslockAccessory) > nexttoward(NSMaxX(screenRect), -INFINITY)) {
+ capslockAccessory.origin.x = NSMaxX(screenRect) - NSWidth(capslockAccessory);
+ }
+ IbeamRect = NSUnionRect(IbeamRect, capslockAccessory);
+ } else {
+ NSRect capslockAccessory = NSMakeRect(NSMinX(IbeamRect), NSMinY(IbeamRect) - 26,
+ NSWidth(IbeamRect), 23);
+ if (NSMinY(capslockAccessory) < nexttoward(NSMinY(screenRect), INFINITY)) {
+ capslockAccessory.origin.y = NSMaxY(screenRect) + 3;
}
+ if (NSMaxY(capslockAccessory) > nexttoward(NSMaxY(screenRect), -INFINITY)) {
+ capslockAccessory.origin.y = NSMaxY(screenRect) - NSHeight(capslockAccessory);
+ }
+ IbeamRect = NSUnionRect(IbeamRect, capslockAccessory);
}
}
}
@@ -787,8 +802,10 @@ - (void)showPanelWithPreedit:(NSString*)preedit
didCompose:(BOOL)didCompose __attribute__((objc_direct)) {
// NSLog(@"showPanelWithPreedit:...:");
SquirrelPanel* panel = NSApp.squirrelAppDelegate.panel;
- panel.IbeamRect = [self getIbeamRect];
- if (NSIsEmptyRect(panel.IbeamRect) && panel.statusMessage.length > 0) {
+ if (NSEqualRects(panel.IbeamRect, NSZeroRect)) {
+ panel.IbeamRect = self.getIbeamRect;
+ }
+ if (NSIsEmptyRect(panel.IbeamRect) && panel.statusMessage != nil) {
[panel updateStatusLong:nil statusShort:nil];
} else {
[panel showPreedit:preedit
@@ -808,16 +825,18 @@ - (void)createSession __attribute__((objc_direct)) {
NSString* app = self.client.bundleIdentifier;
// NSLog(@"createSession: %@", app);
_session = rime_get_api_stdbool()->create_session();
- _schemaId = nil;
+ SquirrelPanel* panel = NSApp.squirrelAppDelegate.panel;
+ _schemaId = panel.optionSwitcher.schemaId.copy;
if (_session != 0) {
- SquirrelConfig* config = [SquirrelConfig.alloc initWithArg:@"squirrel"];
+ SquirrelConfig* config = [SquirrelConfig.alloc initWithType:@".base"];
_appOptions = [config appOptionsForApp:app];
- CGFloat chordDuration = [[config nullableDoubleForOption:@"chord_duration"] doubleValue];
- _chordDuration = chordDuration > 0 ? chordDuration : 0.1;
[config close];
- _panellessCommitFix = [_appOptions boolValueForKey:@"panelless_commit_fix"];
- _inlinePlaceholder = [_appOptions boolValueForKey:@"inline_placeholder"];
- _inlineOffset = [_appOptions intValueForKey:@"inline_offset"];
+ _inlinePreedit = (panel.inlinePreedit && ![_appOptions boolValueForOption:@"no_inline"]) || [_appOptions boolValueForOption:@"inline"];
+ _inlineCandidate = panel.inlineCandidate && ![_appOptions boolValueForOption:@"no_inline"];
+ rime_get_api_stdbool()->set_option(_session, "soft_cursor", !_inlinePreedit);
+ _panellessCommitFix = [_appOptions boolValueForOption:@"panelless_commit_fix"];
+ _inlinePlaceholder = [_appOptions boolValueForOption:@"inline_placeholder"];
+ _inlineOffset = [_appOptions intValueForOption:@"inline_offset"];
if ([app isEqualToString:_currentApp] && _asciiMode >= 0) {
rime_get_api_stdbool()->set_option(_session, "ascii_mode", _asciiMode);
}
@@ -860,14 +879,6 @@ static inline NSUInteger UnicharCount(const char* cString, int length) {
encoding:NSUTF8StringEncoding].length;
}
-static inline NSUInteger fmin(NSUInteger x, NSUInteger y) {
- return x < y ? x : y;
-}
-
-static inline NSUInteger fmax(NSUInteger x, NSUInteger y) {
- return x < y ? y : x;
-}
-
- (void)rimeUpdate __attribute__((objc_direct)) {
// NSLog(@"rimeUpdate");
BOOL didCommit = self.rimeConsumeCommittedText;
@@ -877,7 +888,7 @@ - (void)rimeUpdate __attribute__((objc_direct)) {
RIME_STRUCT(RimeStatus_stdbool, status);
if (rime_get_api_stdbool()->get_status(_session, &status)) {
// enable schema specific ui style
- if (_schemaId == nil || strcmp(_schemaId.UTF8String, status.schema_id) != 0) {
+ if (strcmp(_schemaId.UTF8String, status.schema_id) != 0) {
_schemaId = @(status.schema_id);
_showingSwitcherMenu = rime_get_api_stdbool()->get_option(_session, "dumb");
if (!_showingSwitcherMenu) {
@@ -885,9 +896,9 @@ - (void)rimeUpdate __attribute__((objc_direct)) {
[NSApp.squirrelAppDelegate loadSchemaSpecificSettings:_schemaId
withRimeSession:_session];
// inline preedit
- _inlinePreedit = (panel.inlinePreedit && ![_appOptions boolValueForKey:@"no_inline"]) ||
- [_appOptions boolValueForKey:@"inline"];
- _inlineCandidate = panel.inlineCandidate && ![_appOptions boolValueForKey:@"no_inline"];
+ _inlinePreedit = (panel.inlinePreedit && ![_appOptions boolValueForOption:@"no_inline"]) ||
+ [_appOptions boolValueForOption:@"inline"];
+ _inlineCandidate = panel.inlineCandidate && ![_appOptions boolValueForOption:@"no_inline"];
// if not inline, embed soft cursor in preedit string
rime_get_api_stdbool()->set_option(_session, "soft_cursor", !_inlinePreedit);
} else {
@@ -900,7 +911,7 @@ - (void)rimeUpdate __attribute__((objc_direct)) {
RIME_STRUCT(RimeContext_stdbool, ctx);
if (rime_get_api_stdbool()->get_context(_session, &ctx)) {
- BOOL showingStatus = panel.statusMessage.length > 0;
+ BOOL showingStatus = panel.statusMessage != nil;
// update preedit text
const char* preedit = ctx.composition.preedit;
NSString* preeditText = @(preedit ? : "");
@@ -975,9 +986,7 @@ - (void)rimeUpdate __attribute__((objc_direct)) {
numCandidates + extraCandidates);
_currentIndex = hilitedCandidate + _candidateIndices.location;
- if (showingStatus) {
- [self clearBuffer];
- } else if (_showingSwitcherMenu) {
+ if (_showingSwitcherMenu) {
if (_inlinePlaceholder) {
[self updateComposition];
}
diff --git a/SquirrelPanel.hh b/SquirrelPanel.hh
index 5dfff5de5..62d6b2d0f 100644
--- a/SquirrelPanel.hh
+++ b/SquirrelPanel.hh
@@ -5,27 +5,27 @@
@interface SquirrelPanel : NSPanel
-// Show preedit text inline.
+/// Show preedit text inline.
@property(nonatomic, readonly, direct) BOOL inlinePreedit;
-// Show primary candidate inline
+/// Show primary candidate inline.
@property(nonatomic, readonly, direct) BOOL inlineCandidate;
-// Vertical text orientation, as opposed to horizontal text orientation.
+/// Vertical text orientation, as opposed to horizontal text orientation.
@property(nonatomic, readonly, direct) BOOL vertical;
-// Linear candidate list layout, as opposed to stacked candidate list layout.
+/// Linear candidate list layout, as opposed to stacked candidate list layout.
@property(nonatomic, readonly, direct) BOOL linear;
-// Tabular candidate list layout, initializes as tab-aligned linear layout,
-// expandable to stack 5 (3 for vertical) pages/sections of candidates
+/// Tabular candidate list layout, initializes as tab-aligned linear layout,
+/// expandable to stack 5 (3 for vertical) pages/sections of candidates.
@property(nonatomic, readonly, direct) BOOL tabular;
@property(nonatomic, readonly, direct) BOOL locked;
@property(nonatomic, readonly, direct) BOOL firstLine;
@property(nonatomic, direct) BOOL expanded;
@property(nonatomic, direct) NSUInteger sectionNum;
-// position of the text input I-beam cursor on screen.
+/// Position of the text input I-beam cursor on screen.
@property(nonatomic, direct) NSRect IbeamRect;
@property(nonatomic, readonly, strong, nullable) NSScreen* screen;
-// Status message before pop-up is displayed; nil before normal panel is displayed
+/// Status message before pop-up is displayed; nil before normal panel is displayed.
@property(nonatomic, readonly, strong, nullable, direct) NSString* statusMessage;
-// Store switch options that change style (color theme) settings
+/// Stores switch options that change style (color theme) settings.
@property(nonatomic, strong, nonnull, direct) SquirrelOptionSwitcher* optionSwitcher;
// query
@@ -51,3 +51,10 @@
- (void)updateScriptVariant __attribute__((objc_direct));
@end // SquirrelPanel
+
+extern inline NSUInteger fmin(NSUInteger x, NSUInteger y) {
+ return x < y ? x : y;
+}
+extern inline NSUInteger fmax(NSUInteger x, NSUInteger y) {
+ return x < y ? y : x;
+}
diff --git a/SquirrelPanel.mm b/SquirrelPanel.mm
index be8ea4de9..5945cdcf0 100644
--- a/SquirrelPanel.mm
+++ b/SquirrelPanel.mm
@@ -4,9 +4,9 @@
#import "SquirrelConfig.hh"
#import
-static NSString* const kDefaultCandidateFormat = @"%c. %@";
-static NSString* const kTipSpecifier = @"%s";
+static NSString* const kDefaultCandidateFormat = @"%c. %@ %s";
static NSString* const kFullWidthSpace = @" ";
+static NSString* const kControlCharacterSizeAttributeName = @"ControlCharacterSize";
static const NSTimeInterval kShowStatusDuration = 2.0;
static const CGFloat kBlendedBackgroundColorFraction = 0.2;
static const CGFloat kDefaultFontSize = 24;
@@ -25,9 +25,8 @@ static void rectVertices(NSRect rect, NSPointArray vertices) {
}
typedef struct SquirrelTextPolygon {
- NSRect head;
- NSRect body;
- NSRect tail;
+ NSRect head, body, tail;
+
inline NSPoint origin() {
return (NSIsEmptyRect(head) ? body : head).origin;
}
@@ -39,17 +38,15 @@ inline CGFloat maxY() {
}
inline BOOL separated() {
return !NSIsEmptyRect(head) && NSIsEmptyRect(body) &&
- !NSIsEmptyRect(tail) && NSMaxX(tail) < NSMinX(head) - 0.1;
+ !NSIsEmptyRect(tail) && NSMaxX(tail) < nexttoward(NSMinX(head), -INFINITY);
}
inline BOOL mouseInPolygon(NSPoint point, BOOL flipped) {
return (!NSIsEmptyRect(body) && NSMouseInRect(point, body, flipped)) ||
- (!NSIsEmptyRect(head) && NSMouseInRect(point, head, flipped)) ||
- (!NSIsEmptyRect(tail) && NSMouseInRect(point, tail, flipped));
+ (!NSIsEmptyRect(head) && NSMouseInRect(point, head, flipped)) ||
+ (!NSIsEmptyRect(tail) && NSMouseInRect(point, tail, flipped));
}
void getVertices(NSPointArray vertices) {
- switch ((NSIsEmptyRect(head) << 2) |
- (NSIsEmptyRect(body) << 1) |
- (NSIsEmptyRect(tail) << 0)) {
+ switch ((NSIsEmptyRect(head) << 2) | (NSIsEmptyRect(body) << 1) | (NSIsEmptyRect(tail) << 0)) {
case 0b011:
rectVertices(head, vertices);
break;
@@ -120,6 +117,27 @@ void getVertices(NSPointArray vertices) {
} SquirrelTextPolygon;
+__attribute__((objc_direct_members))
+@interface NSCharacterSet (FullWidthCharacterSets)
+
+@property(nonatomic, readonly, copy, nonnull, class) NSCharacterSet *fullWidthDigitCharacterSet;
+@property(nonatomic, readonly, copy, nonnull, class) NSCharacterSet *fullWidthLatinCapitalCharacterSet;
+
+@end
+
+@implementation NSCharacterSet (FullWidthCharacterSets)
+
++ (NSCharacterSet *)fullWidthDigitCharacterSet {
+ return [NSCharacterSet characterSetWithRange:NSMakeRange(0xFF10, 10)];
+}
+
++ (NSCharacterSet *)fullWidthLatinCapitalCharacterSet {
+ return [NSCharacterSet characterSetWithRange:NSMakeRange(0xFF21, 26)];
+}
+
+@end // NSCharacterSet (FullWidthCharacterSets)
+
+
__attribute__((objc_direct_members))
@interface NSAffineTransform (NSCGAffinTransformConversion)
@@ -150,8 +168,6 @@ - (CGPathRef)quartzPath {
if (@available(macOS 14.0, *)) {
return self.CGPath;
}
- // Need to begin a path here.
- CGPathRef immutablePath = NULL;
// Then draw the path elements.
if (NSInteger numElements = self.elementCount; numElements > 0) {
CGMutablePathRef path = CGPathCreateMutable();
@@ -165,92 +181,56 @@ - (CGPathRef)quartzPath {
CGPathAddLineToPoint(path, NULL, points[0].x, points[0].y);
break;
case NSBezierPathElementCurveTo:
- CGPathAddCurveToPoint(path, NULL, points[0].x, points[0].y,
- points[1].x, points[1].y, points[2].x, points[2].y);
+ CGPathAddCurveToPoint(path, NULL, points[0].x, points[0].y, points[1].x, points[1].y, points[2].x, points[2].y);
break;
case NSBezierPathElementQuadraticCurveTo:
- CGPathAddQuadCurveToPoint(path, NULL, points[0].x, points[0].y,
- points[1].x, points[1].y);
+ CGPathAddQuadCurveToPoint(path, NULL, points[0].x, points[0].y, points[1].x, points[1].y);
break;
case NSBezierPathElementClosePath:
CGPathCloseSubpath(path);
break;
}
}
- immutablePath = (CGPathRef)CFAutorelease(CGPathCreateCopy(path));
+ CGPathRef immutablePath = CGPathCreateCopy(path);
CGPathRelease(path);
+ return (CGPathRef)CFAutorelease(immutablePath);
}
- return immutablePath;
+ return NULL;
}
// Bezier squircle curves, whose rounded corners are smooth (continously differentiable)
+ (NSBezierPath*)squirclePathWithVertices:(NSPointArray)vertices
- count:(NSUInteger)numVert
- cornerRadius:(CGFloat)radius {
- if (vertices == NULL || numVert < 4) {
+ count:(NSUInteger)numVertex
+ cornerRadius:(CGFloat)cornerRadius {
+ if (vertices == NULL || numVertex < 4) {
return nil;
}
NSBezierPath* path = NSBezierPath.bezierPath;
// Always start from the topleft origin going along y axis
- NSPoint point = vertices[numVert - 1];
- NSPoint nextPoint = vertices[0];
- CGVector nextDiff = CGVectorMake(nextPoint.x - point.x, nextPoint.y - point.y);
+ NSPoint vertex = vertices[numVertex - 1];
+ NSPoint nextVertex = vertices[0];
+ CGVector nextDiff = CGVectorMake(nextVertex.x - vertex.x, nextVertex.y - vertex.y);
CGVector lastDiff;
- CGFloat arcRadius = fmin(radius, fabs(nextDiff.dx) * 0.3);
+ CGFloat arcRadius;
NSPoint startPoint;
- NSPoint relayPointA, relayPointB;
- NSPoint controlPointA1, controlPointA2, controlPointB1, controlPointB2;
- NSPoint controlPoint1, controlPoint2;
- NSPoint endPoint = NSMakePoint(point.x + copysign(arcRadius * 1.528664, nextDiff.dx), nextPoint.y);
+ NSPoint endPoint = NSMakePoint(vertex.x + nextDiff.dx * 0.5, vertex.y);
[path moveToPoint:endPoint];
- for (NSUInteger i = 0; i < numVert; ++i) {
+ for (NSUInteger i = 0; i < numVertex; ++i) {
lastDiff = nextDiff;
- point = nextPoint;
- nextPoint = vertices[(i + 1) % numVert];
- nextDiff = CGVectorMake(nextPoint.x - point.x, nextPoint.y - point.y);
+ vertex = nextVertex;
+ nextVertex = vertices[(i + 1) % numVertex];
+ nextDiff = CGVectorMake(nextVertex.x - vertex.x, nextVertex.y - vertex.y);
if (fabs(nextDiff.dx) >= fabs(nextDiff.dy)) {
- arcRadius = fmin(radius, fmin(fabs(nextDiff.dx), fabs(lastDiff.dy)) * 0.3);
- startPoint = NSMakePoint(point.x, fma(copysign(arcRadius, lastDiff.dy), -1.528664, nextPoint.y));
- relayPointA = NSMakePoint(fma(copysign(arcRadius, nextDiff.dx), 0.074911, point.x),
- fma(copysign(arcRadius, lastDiff.dy), -0.631494, nextPoint.y));
- controlPointA1 = NSMakePoint(point.x, fma(copysign(arcRadius, lastDiff.dy), -1.088493, nextPoint.y));
- controlPointA2 = NSMakePoint(point.x, fma(copysign(arcRadius, lastDiff.dy), -0.868407, nextPoint.y));
- relayPointB = NSMakePoint(fma(copysign(arcRadius, nextDiff.dx), 0.631494, point.x),
- fma(copysign(arcRadius, lastDiff.dy), -0.074911, nextPoint.y));
- controlPointB1 = NSMakePoint(fma(copysign(arcRadius, nextDiff.dx), 0.372824, point.x),
- fma(copysign(arcRadius, lastDiff.dy), -0.169060, nextPoint.y));
- controlPointB2 = NSMakePoint(fma(copysign(arcRadius, nextDiff.dx), 0.169060, point.x),
- fma(copysign(arcRadius, lastDiff.dy), -0.372824, nextPoint.y));
- endPoint = NSMakePoint(fma(copysign(arcRadius, nextDiff.dx), 1.528664, point.x), nextPoint.y);
- controlPoint1 = NSMakePoint(fma(copysign(arcRadius, nextDiff.dx), 0.868407, point.x), nextPoint.y);
- controlPoint2 = NSMakePoint(fma(copysign(arcRadius, nextDiff.dx), 1.088493, point.x), nextPoint.y);
+ arcRadius = floor(fmin(fabs(cornerRadius), fmin(fabs(nextDiff.dx), fabs(lastDiff.dy)) * 0.5));
+ startPoint = NSMakePoint(vertex.x, vertex.y - copysign(arcRadius, lastDiff.dy));
+ endPoint = NSMakePoint(vertex.x + copysign(arcRadius, nextDiff.dx), vertex.y);
} else {
- arcRadius = fmin(radius, fmin(fabs(nextDiff.dy), fabs(lastDiff.dx)) * 0.3);
- startPoint = NSMakePoint(fma(copysign(arcRadius, lastDiff.dx), -1.528664, nextPoint.x), point.y);
- relayPointA = NSMakePoint(fma(copysign(arcRadius, lastDiff.dx), -0.631494, nextPoint.x),
- fma(copysign(arcRadius, nextDiff.dy), 0.074911, point.y));
- controlPointA1 = NSMakePoint(fma(copysign(arcRadius, lastDiff.dx), -1.088493, nextPoint.x), point.y);
- controlPointA2 = NSMakePoint(fma(copysign(arcRadius, lastDiff.dx), -0.868407, nextPoint.x), point.y);
- relayPointB = NSMakePoint(fma(copysign(arcRadius, lastDiff.dx), -0.074911, nextPoint.x),
- fma(copysign(arcRadius, nextDiff.dy), 0.631494, point.y));
- controlPointB1 = NSMakePoint(fma(copysign(arcRadius, lastDiff.dx), -0.169060, nextPoint.x),
- fma(copysign(arcRadius, nextDiff.dy), 0.372824, point.y));
- controlPointB2 = NSMakePoint(fma(copysign(arcRadius, lastDiff.dx), -0.372824, nextPoint.x),
- fma(copysign(arcRadius, nextDiff.dy), 0.169060, point.y));
- endPoint = NSMakePoint(nextPoint.x, fma(copysign(arcRadius, nextDiff.dy), 1.528664, point.y));
- controlPoint1 = NSMakePoint(nextPoint.x, fma(copysign(arcRadius, nextDiff.dy), 0.868407, point.y));
- controlPoint2 = NSMakePoint(nextPoint.x, fma(copysign(arcRadius, nextDiff.dy), 1.088493, point.y));
+ arcRadius = floor(fmin(fabs(cornerRadius), fmin(fabs(nextDiff.dy), fabs(lastDiff.dx)) * 0.5));
+ startPoint = NSMakePoint(vertex.x - copysign(arcRadius, lastDiff.dx), vertex.y);
+ endPoint = NSMakePoint(vertex.x, vertex.y + copysign(arcRadius, nextDiff.dy));
}
[path lineToPoint:startPoint];
- [path curveToPoint:relayPointA
- controlPoint1:controlPointA1
- controlPoint2:controlPointA2];
- [path curveToPoint:relayPointB
- controlPoint1:controlPointB1
- controlPoint2:controlPointB2];
- [path curveToPoint:endPoint
- controlPoint1:controlPoint1
- controlPoint2:controlPoint2];
+ [path curveToPoint:endPoint controlPoint1:vertex controlPoint2:vertex];
}
[path closePath];
return path;
@@ -260,8 +240,7 @@ + (NSBezierPath*)squirclePathForRect:(NSRect)rect
cornerRadius:(CGFloat)cornerRadius {
NSPoint vertices[4];
rectVertices(rect, vertices);
- return [NSBezierPath squirclePathWithVertices:vertices
- count:4
+ return [NSBezierPath squirclePathWithVertices:vertices count:4
cornerRadius:cornerRadius];
}
@@ -272,21 +251,18 @@ + (NSBezierPath*)squirclePathForPolygon:(SquirrelTextPolygon)polygon
NSPoint headVertices[4], tailVertices[4];
rectVertices(polygon.head, headVertices);
rectVertices(polygon.tail, tailVertices);
- path = [NSBezierPath squirclePathWithVertices:headVertices
- count:4
+ path = [NSBezierPath squirclePathWithVertices:headVertices count:4
cornerRadius:cornerRadius];
[path appendBezierPath:
- [NSBezierPath squirclePathWithVertices:tailVertices
- count:4
+ [NSBezierPath squirclePathWithVertices:tailVertices count:4
cornerRadius:cornerRadius]];
} else {
- NSUInteger numVert = clamp((NSIsEmptyRect(polygon.head) ? 0 : 4UL) +
- (NSIsEmptyRect(polygon.body) ? 0 : 2UL) +
- (NSIsEmptyRect(polygon.tail) ? 0 : 4UL), 4UL, 8UL);
- NSPoint vertices[numVert];
+ NSUInteger numVertex = clamp((NSIsEmptyRect(polygon.head) ? 0 : 4UL) +
+ (NSIsEmptyRect(polygon.body) ? 0 : 2UL) +
+ (NSIsEmptyRect(polygon.tail) ? 0 : 4UL), 4UL, 8UL);
+ NSPoint vertices[numVertex];
polygon.getVertices(vertices);
- path = [NSBezierPath squirclePathWithVertices:vertices
- count:numVert
+ path = [NSBezierPath squirclePathWithVertices:vertices count:numVertex
cornerRadius:cornerRadius];
}
return path;
@@ -295,6 +271,78 @@ + (NSBezierPath*)squirclePathForPolygon:(SquirrelTextPolygon)polygon
@end // NSBezierPath (BezierPathQuartzUtilities)
+__attribute__((objc_direct_members))
+@implementation NSFontDescriptor (NSFontDescriptorWithFallbackFonts)
+
+static NSArray*>* const features =
+ @[@{NSFontFeatureTypeIdentifierKey: @(kVerticalSubstitutionType),
+ NSFontFeatureSelectorIdentifierKey: @(kSubstituteVerticalFormsOnSelector)},
+ @{NSFontFeatureTypeIdentifierKey: @(kCJKVerticalRomanPlacementType),
+ NSFontFeatureSelectorIdentifierKey: @(kCJKVerticalRomanCenteredSelector)},
+ @{NSFontFeatureTypeIdentifierKey: @(kRubyKanaType),
+ NSFontFeatureSelectorIdentifierKey: @(kRubyKanaOffSelector)}];
+
++ (NSFontDescriptor*)createWithFullname:(NSString*)fullname {
+ if (fullname.length == 0) {
+ return nil;
+ }
+ NSArray* fontNames = [fullname componentsSeparatedByString:@","];
+ NSMutableArray* validFontDescriptors =
+ [NSMutableArray.alloc initWithCapacity:fontNames.count];
+ for (NSString* fontName in fontNames) {
+ if (NSFont* font = [NSFont fontWithName:[fontName stringByTrimmingCharactersInSet:
+ NSCharacterSet.whitespaceAndNewlineCharacterSet]
+ size:0.0]) {
+ /* If the font name is not valid, NSFontDescriptor will still create something for us.
+ However, when we draw the actual text, Squirrel will crash if there is any font descriptor
+ with invalid font name. */
+ NSFontDescriptor* fontDescriptor = [font.fontDescriptor fontDescriptorByAddingAttributes:
+ @{NSFontFeatureSettingsAttribute: features}];
+ NSFontDescriptor* UIFontDescriptor = [fontDescriptor fontDescriptorWithSymbolicTraits:
+ NSFontDescriptorTraitUIOptimized];
+ [validFontDescriptors addObject:[NSFont fontWithDescriptor:UIFontDescriptor size:0.0] != nil ?
+ UIFontDescriptor : fontDescriptor];
+ }
+ }
+ if (validFontDescriptors.count == 0) {
+ return nil;
+ }
+ NSFontDescriptor* initialFontDescriptor = validFontDescriptors[0];
+ NSFontDescriptor* emojiFontDescriptor =
+ [[NSFontDescriptor fontDescriptorWithName:@"AppleColorEmoji" size:0.0]
+ fontDescriptorByAddingAttributes:@{NSFontFeatureSettingsAttribute: features}];
+ NSArray* fallbackDescriptors =
+ [[validFontDescriptors subarrayWithRange:NSMakeRange(1, validFontDescriptors.count - 1)]
+ arrayByAddingObject:emojiFontDescriptor];
+ return [initialFontDescriptor fontDescriptorByAddingAttributes:
+ @{NSFontCascadeListAttribute: fallbackDescriptors}];
+}
+
+@end // NSFontDescriptor (NSFontDescriptorWithFallbackFonts)
+
+
+__attribute__((objc_direct_members))
+@implementation NSFont (NSFontGetLineHeight)
+
+- (CGFloat)lineHeightAsVerticalFont:(BOOL)vertical {
+ NSFont* font = vertical ? self.verticalFont : self;
+ CGFloat lineHeight = ceil(font.ascender - font.descender);
+ NSArray* fallbackList =
+ [font.fontDescriptor objectForKey:NSFontCascadeListAttribute];
+ for (NSFontDescriptor* fallback in fallbackList) {
+ NSFont* fallbackFont = [NSFont fontWithDescriptor:fallback
+ size:font.pointSize];
+ if (vertical) {
+ fallbackFont = fallbackFont.verticalFont;
+ }
+ lineHeight = fmax(lineHeight, ceil(fallbackFont.ascender - fallbackFont.descender));
+ }
+ return lineHeight;
+}
+
+@end // NSFont (NSFontGetLineHeight)
+
+
__attribute__((objc_direct_members))
@implementation NSMutableAttributedString (NSMutableAttributedStringMarkDownFormatting)
@@ -306,7 +354,6 @@ - (void)superscriptionRange:(NSRange)range {
NSFont* font = [NSFont fontWithDescriptor:value.fontDescriptor
size:floor(value.pointSize * 0.55)];
[self addAttributes:@{NSFontAttributeName: font,
- (id)kCTBaselineClassAttributeName: (id)kCTBaselineClassIdeographicCentered,
NSSuperscriptAttributeName: @1}
range:subRange];
}];
@@ -320,7 +367,6 @@ - (void)subscriptionRange:(NSRange)range {
NSFont* font = [NSFont fontWithDescriptor:value.fontDescriptor
size:floor(value.pointSize * 0.55)];
[self addAttributes:@{NSFontAttributeName: font,
- (id)kCTBaselineClassAttributeName: (id)kCTBaselineClassIdeographicCentered,
NSSuperscriptAttributeName: @-1}
range:subRange];
}];
@@ -335,8 +381,7 @@ - (void)formatMarkDown {
options:NSRegularExpressionUseUnicodeWordBoundaries
error:nil];
NSInteger __block offset = 0;
- [regex enumerateMatchesInString:self.mutableString
- options:0
+ [regex enumerateMatchesInString:self.mutableString options:0
range:NSMakeRange(0, self.length)
usingBlock:^(NSTextCheckingResult* _Nullable result,
NSMatchingFlags flags, BOOL* _Nonnull stop) {
@@ -372,7 +417,7 @@ - (void)formatMarkDown {
}
}
-static NSString* const kRubyPattern = @"(\uFFF9\\s*)(\\S+?)(\\s*\uFFFA(.+?)\uFFFB)";
+static NSString* const kRubyPattern = @"(\\x{FFF9}\\s*)(\\S+?)(\\s*\\x{FFFA}(.+?)\\x{FFFB})";
- (CGFloat)annotateRubyInRange:(NSRange)range
verticalOrientation:(BOOL)isVertical
@@ -381,50 +426,56 @@ - (CGFloat)annotateRubyInRange:(NSRange)range
NSRegularExpression* regex = [NSRegularExpression.alloc
initWithPattern:kRubyPattern options:0 error:nil];
CGFloat __block rubyLineHeight;
- [regex enumerateMatchesInString:self.mutableString
- options:0
- range:range
+ [regex enumerateMatchesInString:self.mutableString options:0 range:range
usingBlock:^(NSTextCheckingResult* _Nullable result,
NSMatchingFlags flags, BOOL* _Nonnull stop) {
NSRange baseRange = [result rangeAtIndex:2];
// no ruby annotation if the base string includes line breaks
- if ([self attributedSubstringFromRange:NSMakeRange(0, NSMaxRange(baseRange))].size.width > maxLength - 0.1) {
+ if ([self attributedSubstringFromRange:NSMakeRange(0, NSMaxRange(baseRange))].size.width > nexttoward(maxLength, -INFINITY)) {
[self deleteCharactersInRange:NSMakeRange(NSMaxRange(result.range) - 1, 1)];
[self deleteCharactersInRange:NSMakeRange([result rangeAtIndex:3].location, 1)];
[self deleteCharactersInRange:NSMakeRange([result rangeAtIndex:1].location, 1)];
} else {
- /* base string must use only one font so that all fall within one glyph run and
- the ruby annotation is aligned with no duplicates */
+ // base string must use only one font so that all fall within one glyph run
+ // and the ruby annotation is aligned with no duplicates
NSFont* baseFont = [self attribute:NSFontAttributeName
atIndex:baseRange.location
effectiveRange:NULL];
+ NSString* baseString = [self.mutableString substringWithRange:baseRange];
baseFont = CFBridgingRelease(CTFontCreateForStringWithLanguage
- ((CTFontRef)baseFont, (CFStringRef)self.mutableString,
- CFRangeMake((CFIndex)baseRange.location, (CFIndex)baseRange.length),
+ ((CTFontRef)baseFont, (CFStringRef)baseString,
+ CFRangeMake(0, (CFIndex)baseRange.length),
(CFStringRef)scriptVariant));
- CFStringRef rubyString = (__bridge CFStringRef)[self.mutableString substringWithRange:
- [result rangeAtIndex:4]];
- NSFont* rubyFont = [self attribute:NSFontAttributeName atIndex:[result rangeAtIndex:4].location effectiveRange:NULL];
- rubyFont = [NSFont fontWithDescriptor:rubyFont.fontDescriptor size:ceil(rubyFont.pointSize * 0.5)];
- rubyLineHeight = isVertical ? rubyFont.verticalFont.ascender - rubyFont.verticalFont.descender + 1.0 : rubyFont.ascender - rubyFont.descender + 1.0;
- CFDictionaryRef rubyAttrs = CFDictionaryCreate(NULL, (CFTypeRef[]){kCTFontAttributeName}, (CFTypeRef[]){(__bridge CTFontRef)rubyFont}, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
- CTRubyAnnotationRef rubyAnnotation = CTRubyAnnotationCreateWithAttributes(kCTRubyAlignmentDistributeSpace, kCTRubyOverhangNone, kCTRubyPositionBefore, rubyString, rubyAttrs);
-
- [self deleteCharactersInRange:[result rangeAtIndex:3]];
+ NSString* rubyString = [self.mutableString substringWithRange:[result rangeAtIndex:4]];
+ NSFont* rubyFont = [NSFont fontWithDescriptor:baseFont.fontDescriptor size:baseFont.pointSize * 0.5];
+ rubyLineHeight = [rubyFont lineHeightAsVerticalFont:isVertical];
+ CFStringRef rubyText[kCTRubyPositionCount] = {(__bridge CFStringRef)rubyString, NULL, NULL, NULL};
+ CTRubyAnnotationRef rubyAnnotation = CTRubyAnnotationCreate(kCTRubyAlignmentDistributeSpace,
+ kCTRubyOverhangNone, 0.5, rubyText);
+ [self addAttributes:@{NSFontAttributeName: baseFont,
+ NSVerticalGlyphFormAttributeName: @(isVertical)}
+ range:result.range];
+
if (@available(macOS 12.0, *)) {
+ [self deleteCharactersInRange:[result rangeAtIndex:3]];
} else { // use U+008B as placeholder for line-forward spaces in case ruby is wider than base
+ NSSize baseSize = [self attributedSubstringFromRange:baseRange].size;
+ CGFloat rubyWidth = [self attributedSubstringFromRange:[result rangeAtIndex:4]].size.width * 0.5;
+ [self deleteCharactersInRange:[result rangeAtIndex:3]];
[self replaceCharactersInRange:NSMakeRange(NSMaxRange(baseRange), 0)
withString:[NSString stringWithFormat:@"%C", 0x8B]];
+ [self addAttribute:kControlCharacterSizeAttributeName
+ value:[NSValue valueWithSize:NSMakeSize(fdim(ceil(rubyWidth), floor(baseSize.width)), baseSize.height)]
+ range:NSMakeRange(NSMaxRange(baseRange), 1)];
}
- [self addAttributes:@{(id)kCTRubyAnnotationAttributeName: CFBridgingRelease(rubyAnnotation),
- NSFontAttributeName: baseFont,
- NSVerticalGlyphFormAttributeName: @(isVertical)}
- range:baseRange];
+ [self addAttribute:(id)kCTRubyAnnotationAttributeName
+ value:CFBridgingRelease(rubyAnnotation)
+ range:baseRange];
[self deleteCharactersInRange:[result rangeAtIndex:1]];
}
}];
- [self.mutableString replaceOccurrencesOfString:@"[\uFFF9-\uFFFB]"
- withString:@""
+ [self.mutableString replaceOccurrencesOfString:@"(.)?[\\x{FFF9}-\\x{FFFB}]"
+ withString:@"$1"
options:NSRegularExpressionSearch
range:NSMakeRange(0, self.length)];
return ceil(rubyLineHeight);
@@ -440,26 +491,28 @@ - (NSAttributedString*)attributedStringHorizontalInVerticalForms {
NSMutableDictionary* attrs =
[[self attributesAtIndex:0 effectiveRange:NULL] mutableCopy];
NSFont* font = attrs[NSFontAttributeName];
- CGFloat stringWidth = floor(self.size.width);
- CGFloat height = floor(font.ascender - font.descender);
+ NSAttributedString* attrString = [NSAttributedString.alloc
+ initWithString:self.string
+ attributes:[self fontAttributesInRange:NSMakeRange(0, self.length)]];
+ CGFloat stringWidth = ceil(attrString.size.width);
+ CGFloat height = ceil(attrString.size.height);
CGFloat width = fmax(height, stringWidth);
- NSImage* image = [NSImage imageWithSize:NSMakeSize(height, width)
- flipped:YES
+ NSImage* image = [NSImage imageWithSize:NSMakeSize(height, height) flipped:YES
drawingHandler:^BOOL(NSRect dstRect) {
[NSGraphicsContext saveGraphicsState];
NSAffineTransform* transform = NSAffineTransform.transform;
+ [transform scaleXBy:1.0 yBy:height / width];
+ [transform translateXBy:ceil(height * 0.5) yBy:ceil(width * 0.5)];
[transform rotateByDegrees:-90.0];
[transform concat];
- CGPoint origin = CGPointMake(floor((width - stringWidth) * 0.5 - dstRect.size.height), 0);
- [self drawAtPoint:origin];
+ [attrString drawWithRect:NSMakeRect(-ceil(stringWidth * 0.5), -ceil(height * 0.5), stringWidth, height)
+ options:NSStringDrawingUsesLineFragmentOrigin];
[NSGraphicsContext restoreGraphicsState];
return YES;
}];
- image.resizingMode = NSImageResizingModeStretch;
- image.size = NSMakeSize(height, height);
NSTextAttachment* attm = NSTextAttachment.alloc.init;
attm.image = image;
- attm.bounds = NSMakeRect(0, floor(font.descender), height, height);
+ attm.bounds = NSMakeRect(0, ceil(font.descender), height, height);
attrs[NSAttachmentAttributeName] = attm;
return [NSAttributedString.alloc initWithString:
[NSString stringWithCharacters:(unichar[]){NSAttachmentCharacter} length:1]
@@ -544,9 +597,9 @@ + (NSColor*)colorWithLabLStar:(CGFloat)lStar
bStar:(CGFloat)bStar
alpha:(CGFloat)alpha {
CGFloat components[4];
- components[0] = clamp(lStar, 0.0, 100.0);
- components[1] = clamp(aStar, -127.0, 127.0);
- components[2] = clamp(bStar, -127.0, 127.0);
+ components[0] = clamp(lStar, 0.0, 100.0); // luminance
+ components[1] = clamp(aStar, -127.0, 127.0); // green-red
+ components[2] = clamp(bStar, -127.0, 127.0); // blue-yellow
components[3] = clamp(alpha, 0.0, 1.0);
return [NSColor colorWithColorSpace:NSColorSpace.labColorSpace
components:components count:4];
@@ -616,67 +669,6 @@ - (NSColor*)colorByInvertingLuminanceToExtent:(ColorInversionExtent)extent {
@end // NSColor (colorWithLabColorSpace)
-@implementation NSFontDescriptor (NSFontDescriptorWithFallbackFonts)
-
-+ (NSFontDescriptor*)createWithFullname:(NSString*)fullname {
- if (fullname.length == 0) {
- return nil;
- }
- NSArray* fontNames = [fullname componentsSeparatedByString:@","];
- NSMutableArray* validFontDescriptors =
- [NSMutableArray.alloc initWithCapacity:fontNames.count];
- for (NSString* fontName in fontNames) {
- if (NSFont* font = [NSFont fontWithName:[fontName stringByTrimmingCharactersInSet:
- NSCharacterSet.whitespaceAndNewlineCharacterSet]
- size:0.0]) {
- /* If the font name is not valid, NSFontDescriptor will still create something for us.
- However, when we draw the actual text, Squirrel will crash if there is any font descriptor
- with invalid font name. */
- NSFontDescriptor* fontDescriptor = font.fontDescriptor;
- NSFontDescriptor* UIFontDescriptor = [fontDescriptor fontDescriptorWithSymbolicTraits:
- NSFontDescriptorTraitUIOptimized];
- [validFontDescriptors addObject:[NSFont fontWithDescriptor:UIFontDescriptor
- size:0.0] != nil ?
- UIFontDescriptor : fontDescriptor];
- }
- }
- if (validFontDescriptors.count == 0) {
- return nil;
- }
- NSFontDescriptor* initialFontDescriptor = validFontDescriptors[0];
- NSFontDescriptor* emojiFontDescriptor =
- [NSFontDescriptor fontDescriptorWithName:@"AppleColorEmoji" size:0.0];
- NSArray* fallbackDescriptors =
- [[validFontDescriptors subarrayWithRange:NSMakeRange(1, validFontDescriptors.count - 1)]
- arrayByAddingObject:emojiFontDescriptor];
- return [initialFontDescriptor fontDescriptorByAddingAttributes:
- @{NSFontCascadeListAttribute: fallbackDescriptors}];
-}
-
-@end // NSFontDescriptor (NSFontDescriptorWithFallbackFonts)
-
-
-@implementation NSFont (NSFontGetLineHeight)
-
-- (CGFloat)lineHeightAsVerticalFont:(BOOL)vertical {
- NSFont* font = vertical ? self.verticalFont : self;
- CGFloat lineHeight = ceil(font.ascender - font.descender);
- NSArray* fallbackList =
- [font.fontDescriptor objectForKey:NSFontCascadeListAttribute];
- for (NSFontDescriptor* fallback in fallbackList) {
- NSFont* fallbackFont = [NSFont fontWithDescriptor:fallback
- size:font.pointSize];
- if (vertical) {
- fallbackFont = fallbackFont.verticalFont;
- }
- lineHeight = fmax(lineHeight, ceil(fallbackFont.ascender - fallbackFont.descender));
- }
- return lineHeight;
-}
-
-@end // NSFont (NSFontGetLineHeight)
-
-
#pragma mark - Color scheme and other user configurations
typedef NS_CLOSED_ENUM(BOOL, SquirrelStyle) {
@@ -691,6 +683,34 @@ typedef NS_CLOSED_ENUM(NSUInteger, SquirrelStatusMessageType) {
kStatusMessageTypeLong = 2
};
+typedef NS_CLOSED_ENUM(NSUInteger, SquirrelContentBlock) {
+ kPreeditBlock,
+ kLinearCandidatesBlock,
+ kStackedCandidatesBlock,
+ kPagingBlock,
+ kStatusBlock
+};
+
+__attribute__((objc_direct_members))
+@interface NSFlippedView : NSView
+@end
+
+__attribute__((objc_direct_members))
+@interface SquirrelTextView : NSTextView
+
+@property(nonatomic) SquirrelContentBlock contentBlock;
+
+- (instancetype)initWithContentBlock:(SquirrelContentBlock)contentBlock
+ storage:(NSTextStorage*)textStorage;
+- (NSTextRange*)textRangeFromCharRange:(NSRange)charRange API_AVAILABLE(macos(12.0));
+- (NSRange)charRangeFromTextRange:(NSTextRange*)textRange API_AVAILABLE(macos(12.0));
+- (NSRect)layoutText;
+- (NSRect)blockRectForRange:(NSRange)charRange;
+- (SquirrelTextPolygon)textPolygonForRange:(NSRange)charRange;
+
+@end
+
+
__attribute__((objc_direct_members))
@interface SquirrelTheme : NSObject
@@ -753,12 +773,14 @@ @interface SquirrelTheme : NSObject
@property(nonatomic, readonly, strong, nullable) NSAttributedString* symbolExpand;
@property(nonatomic, readonly, strong, nullable) NSAttributedString* symbolLock;
-@property(nonatomic, readonly, strong, nonnull) NSArray* labels;
+@property(nonatomic, readonly, strong, nonnull) NSArray* rawLabels;
+@property(nonatomic, readonly, strong, nullable) NSArray* labels;
@property(nonatomic, readonly, strong, nonnull) NSAttributedString* candidateTemplate;
@property(nonatomic, readonly, strong, nonnull) NSAttributedString* candidateHilitedTemplate;
@property(nonatomic, readonly, strong, nullable) NSAttributedString* candidateDimmedTemplate;
@property(nonatomic, readonly, strong, nonnull) NSString* selectKeys;
-@property(nonatomic, readonly, strong, nonnull) NSString* candidateFormat;
+@property(nonatomic, readonly, strong, nonnull) NSString* rawCandidateFormat;
+@property(nonatomic, readonly, strong, nullable) NSString* candidateFormat;
@property(nonatomic, readonly, strong, nonnull) NSString* scriptVariant;
@property(nonatomic, readonly) SquirrelStatusMessageType statusMessageType;
@property(nonatomic, readonly) NSUInteger pageSize;
@@ -767,16 +789,16 @@ @interface SquirrelTheme : NSObject
- (instancetype)initWithStyle:(SquirrelStyle)style NS_DESIGNATED_INITIALIZER;
- (void)updateLabelsWithConfig:(SquirrelConfig* _Nonnull)config
directUpdate:(BOOL)update;
-- (void)setSelectKeys:(NSString* _Nonnull)selectKeys
- labels:(NSArray* _Nonnull)labels
- directUpdate:(BOOL)update;
-- (void)setCandidateFormat:(NSString* _Nonnull)candidateFormat;
-- (void)setStatusMessageType:(NSString* _Nullable)type;
+- (void)updateSelectKeys:(NSString* _Nonnull)selectKeys
+ labels:(NSArray* _Nonnull)rawLabels
+ directUpdate:(BOOL)update;
+- (void)updateCandidateFormat:(NSString* _Nonnull)rawCandidateFormat;
+- (void)updateStatusMessageType:(NSString* _Nullable)type;
- (void)updateWithConfig:(SquirrelConfig* _Nonnull)config
styleOptions:(NSSet* _Nonnull)styleOptions
scriptVariant:(NSString* _Nonnull)scriptVariant;
- (void)setAnnotationHeight:(CGFloat)height;
-- (void)setScriptVariant:(NSString* _Nonnull)scriptVariant;
+- (void)updateScriptVariant:(NSString* _Nonnull)scriptVariant;
@end
@@ -792,16 +814,16 @@ - (instancetype)initWithStyle:(SquirrelStyle)style {
if (self = [super init]) {
_style = style;
_selectKeys = @"12345";
- _labels = @[@"1", @"2", @"3", @"4", @"5"];
+ _rawLabels = @[@"1", @"2", @"3", @"4", @"5"];
_pageSize = 5UL;
- _candidateFormat = kDefaultCandidateFormat;
+ _rawCandidateFormat = kDefaultCandidateFormat;
_scriptVariant = @"zh";
NSMutableParagraphStyle* candidateParagraphStyle = NSMutableParagraphStyle.alloc.init;
candidateParagraphStyle.alignment = NSTextAlignmentLeft;
candidateParagraphStyle.lineBreakStrategy = NSLineBreakStrategyNone;
- /* Use left-to-right marks to declare the default writing direction and prevent strong right-to-left
- characters from setting the writing direction in case the label are direction-less symbols */
+ // Use left-to-right marks to declare the default writing direction and prevent strong right-to-left
+ // characters from setting the writing direction in case the label are direction-less symbols
candidateParagraphStyle.baseWritingDirection = NSWritingDirectionLeftToRight;
NSMutableParagraphStyle* preeditParagraphStyle = candidateParagraphStyle.mutableCopy;
NSMutableParagraphStyle* pagingParagraphStyle = candidateParagraphStyle.mutableCopy;
@@ -810,8 +832,10 @@ - (instancetype)initWithStyle:(SquirrelStyle)style {
preeditParagraphStyle.lineBreakMode = NSLineBreakByWordWrapping;
statusParagraphStyle.lineBreakMode = NSLineBreakByTruncatingTail;
- NSFontDescriptor* userFontDesc = [NSFontDescriptor createWithFullname:[NSFont userFontOfSize:0.0].fontName];
- NSFontDescriptor* monoFontDesc = [NSFontDescriptor createWithFullname:[NSFont userFixedPitchFontOfSize:0.0].fontName];
+ NSFontDescriptor* userFontDesc = [NSFontDescriptor createWithFullname:
+ [NSFont userFontOfSize:0.0].fontName];
+ NSFontDescriptor* monoFontDesc = [NSFontDescriptor createWithFullname:
+ [NSFont userFixedPitchFontOfSize:0.0].fontName];
NSFont* userFont = [NSFont fontWithDescriptor:userFontDesc size:kDefaultFontSize];
NSFont* userMonoFont = [NSFont fontWithDescriptor:monoFontDesc size:kDefaultFontSize];
NSFont* monoDigitFont = [NSFont monospacedDigitSystemFontOfSize:kDefaultFontSize
@@ -820,6 +844,7 @@ - (instancetype)initWithStyle:(SquirrelStyle)style {
NSMutableDictionary* textAttrs = NSMutableDictionary.alloc.init;
textAttrs[NSForegroundColorAttributeName] = NSColor.controlTextColor;
textAttrs[NSFontAttributeName] = userFont;
+ textAttrs[NSKernAttributeName] = @0;
// Use left-to-right embedding to prevent right-to-left text from changing the layout of the candidate.
textAttrs[NSWritingDirectionAttributeName] = @[@0];
textAttrs[NSParagraphStyleAttributeName] = candidateParagraphStyle;
@@ -827,12 +852,14 @@ - (instancetype)initWithStyle:(SquirrelStyle)style {
NSMutableDictionary* labelAttrs = textAttrs.mutableCopy;
labelAttrs[NSForegroundColorAttributeName] = NSColor.secondaryLabelColor;
labelAttrs[NSFontAttributeName] = userMonoFont;
+ labelAttrs[NSKernAttributeName] = @0;
labelAttrs[NSStrokeWidthAttributeName] = @(-2.0 / kDefaultFontSize);
labelAttrs[NSParagraphStyleAttributeName] = candidateParagraphStyle;
NSMutableDictionary* commentAttrs = NSMutableDictionary.alloc.init;
commentAttrs[NSForegroundColorAttributeName] = NSColor.secondaryLabelColor;
commentAttrs[NSFontAttributeName] = userFont;
+ commentAttrs[NSKernAttributeName] = @0;
commentAttrs[NSParagraphStyleAttributeName] = candidateParagraphStyle;
NSMutableDictionary* preeditAttrs = NSMutableDictionary.alloc.init;
@@ -870,7 +897,10 @@ - (instancetype)initWithStyle:(SquirrelStyle)style {
_hilitedCommentForeColor = NSColor.alternateSelectedControlTextColor;
_hilitedLabelForeColor = NSColor.alternateSelectedControlTextColor;
- [self updateCandidateFormatForAttributesOnly:NO];
+ CGGlyph glyphs[1];
+ CTFontGetGlyphsForCharacters((__bridge CTFontRef)userFont, (unichar[1]){0x3000}, glyphs, 1);
+ _fullWidth = ceil([kFullWidthSpace sizeWithAttributes:@{NSFontAttributeName: userFont}].width);
+ [self updateCandidatetemplates];
[self updateSeperatorAndSymbolAttrs];
}
return self;
@@ -975,79 +1005,80 @@ - (void)updateSeperatorAndSymbolAttrs {
- (void)updateLabelsWithConfig:(SquirrelConfig*)config
directUpdate:(BOOL)update {
NSUInteger menuSize = (NSUInteger)[config intValueForOption:@"menu/page_size"] ? : 5;
- NSMutableArray* labels = [NSMutableArray.alloc initWithCapacity:menuSize];
- NSString* selectKeys = [config stringForOption:@"menu/alternative_select_keys"];
+ NSString* selectKeys = [([config stringForOption:@"menu/alternative_select_keys"] ? : @"1234567890") substringToIndex:menuSize];
NSArray* selectLabels = [config listForOption:@"menu/alternative_select_labels"];
- if (selectLabels.count > 0) {
- [labels addObjectsFromArray:
- [selectLabels subarrayWithRange:NSMakeRange(0, menuSize)]];
- }
- if (selectKeys != nil) {
- if (selectLabels.count == 0) {
- NSString* keyCaps = [selectKeys.uppercaseString stringByApplyingTransform:
- NSStringTransformFullwidthToHalfwidth reverse:YES];
- for (NSUInteger i = 0; i < menuSize; ++i) {
- labels[i] = [keyCaps substringWithRange:NSMakeRange(i, 1)];
- }
+ NSMutableArray* rawLabels = [NSMutableArray.alloc initWithCapacity:menuSize];
+ if (selectLabels == nil) {
+ NSString* labelString = [selectKeys.uppercaseString
+ stringByApplyingTransform:NSStringTransformFullwidthToHalfwidth
+ reverse:YES];
+ for (NSUInteger i = 0; i < menuSize; ++i) {
+ rawLabels[i] = [labelString substringWithRange:NSMakeRange(i, 1)];
}
} else {
- selectKeys = [@"1234567890" substringToIndex:menuSize];
- if (selectLabels.count == 0) {
- NSString* numerals = [selectKeys stringByApplyingTransform:
- NSStringTransformFullwidthToHalfwidth reverse:YES];
- for (NSUInteger i = 0; i < menuSize; ++i) {
- labels[i] = [numerals substringWithRange:NSMakeRange(i, 1)];
- }
+ for (NSUInteger i = 0; i < menuSize; ++i) {
+ rawLabels[i] = selectLabels[i];
}
}
- [self setSelectKeys:selectKeys
- labels:labels
- directUpdate:update];
+ [self updateSelectKeys:selectKeys labels:rawLabels directUpdate:update];
}
-- (void)setSelectKeys:(NSString*)selectKeys
- labels:(NSArray*)labels
- directUpdate:(BOOL)update {
+- (void)updateSelectKeys:(NSString*)selectKeys
+ labels:(NSArray*)rawLabels
+ directUpdate:(BOOL)update {
+ if ([_selectKeys isEqualToString:selectKeys] && [_rawLabels isEqualToArray:rawLabels]) {
+ return;
+ }
_selectKeys = selectKeys;
- _labels = labels;
- _pageSize = labels.count;
+ _rawLabels = rawLabels;
+ _pageSize = rawLabels.count;
+ _labels = nil;
if (update) {
- [self updateCandidateFormatForAttributesOnly:YES];
+ [self updateCandidatetemplates];
}
}
-- (void)setCandidateFormat:(NSString*)candidateFormat {
- BOOL attrsOnly = [candidateFormat isEqualToString:_candidateFormat];
- if (!attrsOnly) {
- _candidateFormat = candidateFormat;
+- (void)updateCandidateFormat:(NSString*)rawCandidateFormat {
+ if (![_rawCandidateFormat isEqualToString:rawCandidateFormat]) {
+ _rawCandidateFormat = rawCandidateFormat;
+ _candidateFormat = nil;
}
- [self updateCandidateFormatForAttributesOnly:attrsOnly];
+ [self updateCandidatetemplates];
[self updateSeperatorAndSymbolAttrs];
}
-- (void)updateCandidateFormatForAttributesOnly:(BOOL)attrsOnly {
- NSMutableAttributedString* candidateTemplate;
- if (!attrsOnly) {
+- (void)updateCandidatetemplates {
+ if (_candidateFormat.length == 0 || _labels.count == 0) {
// validate candidate format: must have enumerator '%c' before candidate '%@'
- NSMutableString* candidateFormat = _candidateFormat.mutableCopy;
- if (![candidateFormat containsString:@"%@"]) {
+ NSMutableString* candidateFormat = _rawCandidateFormat.mutableCopy;
+ NSRange textRange = [candidateFormat rangeOfString:@"%@" options:NSLiteralSearch];
+ if (textRange.length == 0) {
[candidateFormat appendString:@"%@"];
}
NSRange labelRange = [candidateFormat rangeOfString:@"%c" options:NSLiteralSearch];
if (labelRange.length == 0) {
[candidateFormat insertString:@"%c" atIndex:0];
+ labelRange = [candidateFormat rangeOfString:@"%c" options:NSLiteralSearch];
}
- NSRange textRange = [candidateFormat rangeOfString:@"%@" options:NSLiteralSearch];
+ textRange = [candidateFormat rangeOfString:@"%@" options:NSLiteralSearch];
if (labelRange.location > textRange.location) {
candidateFormat.string = kDefaultCandidateFormat;
}
+ textRange = [candidateFormat rangeOfString:@"(\\x{FFF9})?%@" options:NSRegularExpressionSearch];
+ NSRange commentRange = NSMakeRange(NSMaxRange(textRange), candidateFormat.length - NSMaxRange(textRange));
+ if (commentRange.length == 0 || ![[candidateFormat substringWithRange:commentRange] containsString:@"%s"]) {
+ [candidateFormat insertString:@"%s" atIndex:commentRange.location];
+ }
+ if (!_linear) {
+ [candidateFormat insertString:@"\t" atIndex:textRange.location];
+ }
+ _candidateFormat = candidateFormat;
- NSMutableArray* labels = _labels.mutableCopy;
+ NSMutableArray* labels = _rawLabels.mutableCopy;
NSRange enumRange = NSMakeRange(0, 0);
NSCharacterSet* labelCharacters = [NSCharacterSet characterSetWithCharactersInString:
[labels componentsJoinedByString:@""]];
- if ([[NSCharacterSet characterSetWithRange:NSMakeRange(0xFF10, 10)]
- isSupersetOfSet:labelCharacters]) { // 01..9
+ if ([NSCharacterSet.fullWidthDigitCharacterSet isSupersetOfSet:labelCharacters]) { // 01..9
if ((enumRange = [candidateFormat rangeOfString:@"%c\u20E3"
options:NSLiteralSearch]).length > 0) { // 1︎⃣...9︎⃣0︎⃣
for (NSUInteger i = 0; i < labels.count; ++i) {
@@ -1058,15 +1089,15 @@ - (void)updateCandidateFormatForAttributesOnly:(BOOL)attrsOnly {
options:NSLiteralSearch]).length > 0) { // ①...⑨⓪
for (NSUInteger i = 0; i < labels.count; ++i) {
labels[i] = [NSString stringWithFormat:@"%C",
- (unichar)([labels[i] characterAtIndex:0] == 0xFF10 ? 0x24EA :
- [labels[i] characterAtIndex:0] - 0xFF11 + 0x2460)];
+ (unichar)([labels[i] characterAtIndex:0] == 0xFF10 ? 0x24EA :
+ [labels[i] characterAtIndex:0] - 0xFF11 + 0x2460)];
}
} else if ((enumRange = [candidateFormat rangeOfString:@"(%c)"
options:NSLiteralSearch]).length > 0) { // ⑴...⑼⑽
for (NSUInteger i = 0; i < labels.count; ++i) {
labels[i] = [NSString stringWithFormat:@"%C",
- (unichar)([labels[i] characterAtIndex:0] == 0xFF10 ? 0x247D :
- [labels[i] characterAtIndex:0] - 0xFF11 + 0x2474)];
+ (unichar)([labels[i] characterAtIndex:0] == 0xFF10 ? 0x247D :
+ [labels[i] characterAtIndex:0] - 0xFF11 + 0x2474)];
}
} else if ((enumRange = [candidateFormat rangeOfString:@"%c."
options:NSLiteralSearch]).length > 0) { // ⒈...⒐🄀
@@ -1081,8 +1112,7 @@ - (void)updateCandidateFormatForAttributesOnly:(BOOL)attrsOnly {
(const unichar[2]){0xD83C, (unichar)([labels[i] characterAtIndex:0] - 0xFF10 + 0xDD01)}];
}
}
- } else if ([[NSCharacterSet characterSetWithRange:NSMakeRange(0xFF21, 26)]
- isSupersetOfSet:labelCharacters]) { // A..Z
+ } else if ([NSCharacterSet.fullWidthLatinCapitalCharacterSet isSupersetOfSet:labelCharacters]) { // A..Z
if ((enumRange = [candidateFormat rangeOfString:@"%c\u20DD"
options:NSLiteralSearch]).length > 0) { // Ⓐ...Ⓩ
for (NSUInteger i = 0; i < labels.count; ++i) {
@@ -1105,85 +1135,53 @@ - (void)updateCandidateFormatForAttributesOnly:(BOOL)attrsOnly {
}
if (enumRange.length > 0) {
[candidateFormat replaceCharactersInRange:enumRange withString:@"%c"];
- _labels = labels;
}
- candidateTemplate = [NSMutableAttributedString.alloc initWithString:candidateFormat];
- } else {
- candidateTemplate = _candidateTemplate.mutableCopy;
+ _labels = labels;
}
+
// make sure label font can render all label strings
- NSString* labelString = [_labels componentsJoinedByString:@""];
+ NSMutableDictionary* textAttrs = _textAttrs.mutableCopy;
+ NSMutableDictionary* commentAttrs = _commentAttrs.mutableCopy;
NSMutableDictionary* labelAttrs = _labelAttrs.mutableCopy;
+ NSString* labelString = [_rawLabels componentsJoinedByString:@""];
NSFont* labelFont = labelAttrs[NSFontAttributeName];
NSFont* substituteFont = CFBridgingRelease(CTFontCreateForString((CTFontRef)labelFont,
(CFStringRef)labelString, CFRangeMake(0, (CFIndex)labelString.length)));
if ([substituteFont isNotEqualTo:labelFont]) {
- NSDictionary* monoDigitAttrs =
- @{NSFontFeatureSettingsAttribute: @[@{NSFontFeatureTypeIdentifierKey: @(kNumberSpacingType),
- NSFontFeatureSelectorIdentifierKey: @(kMonospacedNumbersSelector)},
- @{NSFontFeatureTypeIdentifierKey: @(kTextSpacingType),
- NSFontFeatureSelectorIdentifierKey: @(kHalfWidthTextSelector)}]};
- NSFontDescriptor* substituteFontDescriptor = [substituteFont.fontDescriptor
- fontDescriptorByAddingAttributes:monoDigitAttrs];
- substituteFont = [NSFont fontWithDescriptor:substituteFontDescriptor size:labelFont.pointSize];
- labelAttrs[NSFontAttributeName] = substituteFont;
- }
-
- NSRange textRange = [candidateTemplate.mutableString rangeOfString:@"%@" options:NSLiteralSearch];
- NSRange labelRange = NSMakeRange(0, textRange.location);
- NSRange commentRange = NSMakeRange(NSMaxRange(textRange),
- candidateTemplate.length - NSMaxRange(textRange));
- [candidateTemplate setAttributes:_labelAttrs range:labelRange];
- [candidateTemplate setAttributes:_textAttrs range:textRange];
- if (commentRange.length > 0) {
- [candidateTemplate setAttributes:_commentAttrs range:commentRange];
+ labelAttrs[NSFontAttributeName] = CFBridgingRelease(CTFontCreateForString((CTFontRef)substituteFont,
+ (CFStringRef)labelString, CFRangeMake(0, (CFIndex)labelString.length)));
}
+
// parse markdown formats
- if (!attrsOnly) {
- [candidateTemplate formatMarkDown];
- // add placeholder for comment '%s'
- textRange = [candidateTemplate.mutableString rangeOfString:@"%@" options:NSLiteralSearch];
- labelRange = NSMakeRange(0, textRange.location);
- commentRange = NSMakeRange(NSMaxRange(textRange),
- candidateTemplate.length - NSMaxRange(textRange));
- if (commentRange.length > 0) {
- [candidateTemplate replaceCharactersInRange:commentRange
- withString:[kTipSpecifier append:[candidateTemplate.mutableString
- substringWithRange:commentRange]]];
- } else {
- [candidateTemplate appendAttributedString:
- [NSAttributedString.alloc initWithString:kTipSpecifier
- attributes:_commentAttrs]];
- }
- commentRange.length += kTipSpecifier.length;
- if (!_linear) {
- [candidateTemplate replaceCharactersInRange:NSMakeRange(textRange.location, 0)
- withString:@"\t"];
- labelRange.length += 1;
- textRange.location += 1;
- commentRange.location += 1;
- }
- }
+ NSMutableAttributedString* candidateTemplate = [NSMutableAttributedString.alloc initWithString:_candidateFormat];
+ NSRange textRange = [candidateTemplate.mutableString rangeOfString:@"(\\x{FFF9})?%@" options:NSRegularExpressionSearch];
+ NSRange labelRange = NSMakeRange(0, textRange.location);
+ NSRange commentRange = NSMakeRange(NSMaxRange(textRange), candidateTemplate.length - NSMaxRange(textRange));
+ [candidateTemplate setAttributes:labelAttrs range:labelRange];
+ [candidateTemplate setAttributes:textAttrs range:textRange];
+ [candidateTemplate setAttributes:commentAttrs range:commentRange];
+ [candidateTemplate formatMarkDown];
+ textRange = [candidateTemplate.mutableString rangeOfString:@"(\\x{FFF9})?%@" options:NSRegularExpressionSearch];
+ labelRange = NSMakeRange(0, textRange.location);
+ commentRange = NSMakeRange(NSMaxRange(textRange), candidateTemplate.length - NSMaxRange(textRange));
+
// for stacked layout, calculate head indent
NSMutableParagraphStyle* candidateParagraphStyle = _candidateParagraphStyle.mutableCopy;
if (!_linear) {
- CGFloat indent = 0.0;
- NSAttributedString* labelFormat = [candidateTemplate attributedSubstringFromRange:
- NSMakeRange(0, labelRange.length - 1)];
+ NSRange enumRange = [candidateTemplate.mutableString rangeOfString:@"%c" options:NSLiteralSearch];
+ NSTextStorage* textStorage = NSTextStorage.alloc.init;
+ SquirrelTextView* textView = [SquirrelTextView.alloc initWithContentBlock:kStackedCandidatesBlock storage:textStorage];
+ textView.layoutOrientation = _vertical ? NSTextLayoutOrientationVertical : NSTextLayoutOrientationHorizontal;
for (NSString* label in _labels) {
- NSMutableAttributedString* enumString = labelFormat.mutableCopy;
- NSRange enumRange = [enumString.mutableString rangeOfString:@"%c" options:NSLiteralSearch];
- [enumString.mutableString replaceCharactersInRange:enumRange withString:label];
- [enumString addAttribute:NSVerticalGlyphFormAttributeName
- value:@(_vertical)
- range:NSMakeRange(enumRange.location, label.length)];
- indent = fmax(indent, enumString.size.width);
+ NSMutableAttributedString* labelString = [candidateTemplate attributedSubstringFromRange:NSMakeRange(0, labelRange.length - 1)].mutableCopy;
+ [labelString replaceCharactersInRange:enumRange withString:label];
+ [textStorage appendAttributedString:labelString];
+ [textStorage appendAttributedString:[NSAttributedString.alloc initWithString:@"\n"]];
}
- indent = floor(indent) + 1.0;
- candidateParagraphStyle.tabStops = @[[NSTextTab.alloc
- initWithTextAlignment:NSTextAlignmentLeft
- location:indent
- options:@{}]];
+ CGFloat indent = floor(NSMaxX(textView.layoutText)) + 1.0;
+ candidateParagraphStyle.tabStops = @[[NSTextTab.alloc initWithTextAlignment:NSTextAlignmentLeft
+ location:indent
+ options:@{}]];
candidateParagraphStyle.headIndent = indent;
_candidateParagraphStyle = candidateParagraphStyle;
_truncatedParagraphStyle = nil;
@@ -1197,8 +1195,6 @@ - (void)updateCandidateFormatForAttributesOnly:(BOOL)attrsOnly {
_truncatedParagraphStyle = truncatedParagraphStyle;
}
- NSMutableDictionary* textAttrs = _textAttrs.mutableCopy;
- NSMutableDictionary* commentAttrs = _commentAttrs.mutableCopy;
textAttrs[NSParagraphStyleAttributeName] = candidateParagraphStyle;
commentAttrs[NSParagraphStyleAttributeName] = candidateParagraphStyle;
labelAttrs[NSParagraphStyleAttributeName] = candidateParagraphStyle;
@@ -1210,6 +1206,7 @@ - (void)updateCandidateFormatForAttributesOnly:(BOOL)attrsOnly {
value:candidateParagraphStyle
range:NSMakeRange(0, candidateTemplate.length)];
_candidateTemplate = candidateTemplate;
+
NSMutableAttributedString* candidateHilitedTemplate = candidateTemplate.mutableCopy;
[candidateHilitedTemplate addAttribute:NSForegroundColorAttributeName
value:_hilitedLabelForeColor
@@ -1221,6 +1218,7 @@ - (void)updateCandidateFormatForAttributesOnly:(BOOL)attrsOnly {
value:_hilitedCommentForeColor
range:commentRange];
_candidateHilitedTemplate = candidateHilitedTemplate;
+
if (_tabular) {
NSMutableAttributedString* candidateDimmedTemplate = candidateTemplate.mutableCopy;
[candidateDimmedTemplate addAttribute:NSForegroundColorAttributeName
@@ -1232,7 +1230,7 @@ - (void)updateCandidateFormatForAttributesOnly:(BOOL)attrsOnly {
}
}
-- (void)setStatusMessageType:(NSString*)type {
+- (void)updateStatusMessageType:(NSString*)type {
if ([@"long" caseInsensitiveCompare:type] == NSOrderedSame) {
_statusMessageType = kStatusMessageTypeLong;
} else if ([@"short" caseInsensitiveCompare:type] == NSOrderedSame) {
@@ -1283,10 +1281,16 @@ static void updateTextOrientation(BOOL* isVertical, SquirrelConfig* config, NSSt
if (newValue != nil) *existing = newValue;
}
+static NSArray*>* monoDigitFeatures =
+ @[@{NSFontFeatureTypeIdentifierKey: @(kNumberSpacingType),
+ NSFontFeatureSelectorIdentifierKey: @(kMonospacedNumbersSelector)},
+ @{NSFontFeatureTypeIdentifierKey: @(kTextSpacingType),
+ NSFontFeatureSelectorIdentifierKey: @(kHalfWidthTextSelector)}];
+
- (void)updateWithConfig:(SquirrelConfig*)config
styleOptions:(NSSet*)styleOptions
scriptVariant:(NSString*)scriptVariant {
- /*** INTERFACE ***/
+ /* INTERFACE */
BOOL linear = NO;
BOOL tabular = NO;
BOOL vertical = NO;
@@ -1297,8 +1301,8 @@ - (void)updateWithConfig:(SquirrelConfig*)config
NSNumber* showPaging = [config nullableBoolForOption:@"style/show_paging"];
NSNumber* rememberSize = [config nullableBoolForOption:@"style/remember_size" alias:@"memorize_size"];
NSString* statusMessageType = [config stringForOption:@"style/status_message_type"];
- NSString* candidateFormat = [config stringForOption:@"style/candidate_format"];
- /*** TYPOGRAPHY ***/
+ NSString* rawCandidateFormat = [config stringForOption:@"style/candidate_format"];
+ /* TYPOGRAPHY */
NSString* fontName = [config stringForOption:@"style/font_face"];
NSNumber* fontSize = [config nullableDoubleForOption:@"style/font_point" constraint:pos_round];
NSString* labelFontName = [config stringForOption:@"style/label_font_face"];
@@ -1317,7 +1321,7 @@ - (void)updateWithConfig:(SquirrelConfig*)config
NSNumber* baseOffset = [config nullableDoubleForOption:@"style/base_offset"];
NSNumber* lineLength = [config nullableDoubleForOption:@"style/line_length"];
NSNumber* shadowSize = [config nullableDoubleForOption:@"style/shadow_size" constraint:positive];
- /*** CHROMATICS ***/
+ /* CHROMATICS */
NSColor* backColor;
NSColor* borderColor;
NSColor* preeditBackColor;
@@ -1359,7 +1363,7 @@ - (void)updateWithConfig:(SquirrelConfig*)config
}
// get color scheme and then check possible overrides from styleSwitcher
for (NSString* prefix in configPrefixes) {
- /*** CHROMATICS override ***/
+ /* CHROMATICS override */
if (NSString* colorSpace = [config stringForOption:[prefix append:@"/color_space"]]) {
config.colorSpace = colorSpace;
}
@@ -1380,9 +1384,9 @@ - (void)updateWithConfig:(SquirrelConfig*)config
update(&hilitedLabelForeColor, [config colorForOption:[prefix append:@"/label_hilited_color"] alias:@"hilited_candidate_label_color"]);
update(&backImage, [config imageForOption:[prefix append:@"/back_image"]]);
- /* the following per-color-scheme configurations, if exist, will
- override configurations with the same name under the global 'style' section */
- /*** INTERFACE override ***/
+ // the following per-color-scheme configurations, if exist, will
+ // override configurations with the same name under the global 'style' section
+ /* INTERFACE override */
updateCandidateListLayout(&linear, &tabular, config, prefix);
updateTextOrientation(&vertical, config, prefix);
update(&inlinePreedit, [config nullableBoolForOption:[prefix append:@"/inline_preedit"]]);
@@ -1390,8 +1394,8 @@ - (void)updateWithConfig:(SquirrelConfig*)config
update(&showPaging, [config nullableBoolForOption:[prefix append:@"/show_paging"]]);
update(&rememberSize, [config nullableBoolForOption:[prefix append:@"/remember_size"] alias:@"memorize_size"]);
update(&statusMessageType, [config stringForOption:[prefix append:@"/status_message_type"]]);
- update(&candidateFormat, [config stringForOption:[prefix append:@"/candidate_format"]]);
- /*** TYPOGRAPHY override ***/
+ update(&rawCandidateFormat, [config stringForOption:[prefix append:@"/candidate_format"]]);
+ /* TYPOGRAPHY override */
update(&fontName, [config stringForOption:[prefix append:@"/font_face"]]);
update(&fontSize, [config nullableDoubleForOption:[prefix append:@"/font_point"] constraint:pos_round]);
update(&labelFontName, [config stringForOption:[prefix append:@"/label_font_face"]]);
@@ -1412,46 +1416,45 @@ - (void)updateWithConfig:(SquirrelConfig*)config
update(&shadowSize, [config nullableDoubleForOption:[prefix append:@"/shadow_size"] constraint:positive]);
}
- /*** TYPOGRAPHY refinement ***/
+ /* FORMAT reset */
+ rawCandidateFormat = rawCandidateFormat ? : kDefaultCandidateFormat;
+ if (_linear != linear) {
+ _candidateFormat = @""; // reset format after switching between linear and stacked
+ }
+
+ /* TYPOGRAPHY refinement */
fontSize = fontSize ? : @(kDefaultFontSize);
labelFontSize = labelFontSize ? : fontSize;
commentFontSize = commentFontSize ? : fontSize;
- NSDictionary* monoDigitAttrs =
- @{NSFontFeatureSettingsAttribute: @[@{NSFontFeatureTypeIdentifierKey: @(kNumberSpacingType),
- NSFontFeatureSelectorIdentifierKey: @(kMonospacedNumbersSelector)},
- @{NSFontFeatureTypeIdentifierKey: @(kTextSpacingType),
- NSFontFeatureSelectorIdentifierKey: @(kHalfWidthTextSelector)}]};
-
- NSFontDescriptor* fontDescriptor = [NSFontDescriptor createWithFullname:fontName];
- NSFont* font = [NSFont fontWithDescriptor:fontDescriptor ? : [NSFontDescriptor createWithFullname:[NSFont userFontOfSize:0].fontName]
- size:fontSize.doubleValue];
+ NSFontDescriptor* fontDescriptor = [NSFontDescriptor createWithFullname:fontName] ? :
+ [NSFontDescriptor createWithFullname:[NSFont userFontOfSize:0].fontName];
+ NSFont* font = [NSFont fontWithDescriptor:fontDescriptor
+ size:fontSize.doubleValue];
NSFontDescriptor* labelFontDescriptor = [([NSFontDescriptor createWithFullname:labelFontName] ? : fontDescriptor)
- fontDescriptorByAddingAttributes:monoDigitAttrs];
- NSFont* labelFont = labelFontDescriptor ? [NSFont fontWithDescriptor:labelFontDescriptor
- size:labelFontSize.doubleValue]
- : [NSFont monospacedDigitSystemFontOfSize:labelFontSize.doubleValue
- weight:NSFontWeightRegular];
-
- NSFontDescriptor* commentFontDescriptor = [NSFontDescriptor createWithFullname:commentFontName];
- NSFont* commentFont = [NSFont fontWithDescriptor:commentFontDescriptor ? : fontDescriptor
+ fontDescriptorByAddingAttributes:@{NSFontFeatureSettingsAttribute: monoDigitFeatures}];
+ NSFont* labelFont = [NSFont fontWithDescriptor:labelFontDescriptor
+ size:labelFontSize.doubleValue];
+ NSFont* commentFont = [NSFont fontWithDescriptor:[NSFontDescriptor createWithFullname:commentFontName] ? : fontDescriptor
size:commentFontSize.doubleValue];
-
- NSFont* pagingFont = [NSFont monospacedDigitSystemFontOfSize:labelFontSize.doubleValue
- weight:NSFontWeightRegular];
+ NSFont* systemFont = [NSFont systemFontOfSize:labelFontSize.doubleValue];
+ NSFontDescriptor* pagingFontDescriptor = [labelFont.fontDescriptor fontDescriptorByAddingAttributes:
+ @{NSFontCascadeListAttribute: @[systemFont.fontDescriptor]}];
+ NSFont* pagingFont = [NSFont fontWithDescriptor:pagingFontDescriptor
+ size:labelFontSize.doubleValue];
CGFloat fontHeight = [font lineHeightAsVerticalFont:vertical];
CGFloat labelFontHeight = [labelFont lineHeightAsVerticalFont:vertical];
CGFloat commentFontHeight = [commentFont lineHeightAsVerticalFont:vertical];
+ CGFloat pagingFontHeight = [pagingFont lineHeightAsVerticalFont:NO];
CGFloat lineHeight = fmax(fontHeight, fmax(labelFontHeight, commentFontHeight));
- CGFloat fullWidth = ceil([kFullWidthSpace sizeWithAttributes:
- @{NSFontAttributeName : commentFont}].width);
+ CGFloat fullWidth = ceil([kFullWidthSpace sizeWithAttributes:@{NSFontAttributeName: commentFont}].width);
NSMutableParagraphStyle* candidateParagraphStyle = _candidateParagraphStyle.mutableCopy;
candidateParagraphStyle.minimumLineHeight = lineHeight;
candidateParagraphStyle.maximumLineHeight = lineHeight;
- candidateParagraphStyle.paragraphSpacingBefore = linear ? 0.0 : ceil(lineSpacing.doubleValue * 0.5);
- candidateParagraphStyle.paragraphSpacing = linear ? 0.0 : floor(lineSpacing.doubleValue * 0.5);
+ candidateParagraphStyle.paragraphSpacingBefore = linear ? 0.0 : floor(lineSpacing.doubleValue * 0.5);
+ candidateParagraphStyle.paragraphSpacing = linear ? 0.0 : ceil(lineSpacing.doubleValue * 0.5);
candidateParagraphStyle.lineSpacing = linear ? lineSpacing.doubleValue : 0.0;
candidateParagraphStyle.tabStops = @[];
candidateParagraphStyle.defaultTabInterval = fullWidth * 2;
@@ -1459,12 +1462,11 @@ - (void)updateWithConfig:(SquirrelConfig*)config
NSMutableParagraphStyle* preeditParagraphStyle = _preeditParagraphStyle.mutableCopy;
preeditParagraphStyle.minimumLineHeight = fontHeight;
preeditParagraphStyle.maximumLineHeight = fontHeight;
- preeditParagraphStyle.paragraphSpacing = spacing.doubleValue;
preeditParagraphStyle.tabStops = @[];
NSMutableParagraphStyle* pagingParagraphStyle = _pagingParagraphStyle.mutableCopy;
- pagingParagraphStyle.minimumLineHeight = ceil(pagingFont.ascender - pagingFont.descender);
- pagingParagraphStyle.maximumLineHeight = ceil(pagingFont.ascender - pagingFont.descender);
+ pagingParagraphStyle.minimumLineHeight = pagingFontHeight;
+ pagingParagraphStyle.maximumLineHeight = pagingFontHeight;
pagingParagraphStyle.tabStops = @[];
NSMutableParagraphStyle* statusParagraphStyle = _statusParagraphStyle.mutableCopy;
@@ -1485,6 +1487,9 @@ - (void)updateWithConfig:(SquirrelConfig*)config
pagingAttrs[NSFontAttributeName] = pagingFont;
statusAttrs[NSFontAttributeName] = commentFont;
labelAttrs[NSStrokeWidthAttributeName] = @(-2.0 / labelFontSize.doubleValue);
+ textAttrs[NSKernAttributeName] = vertical ? @(0.1 * fontSize.doubleValue) : @(0.0);
+ labelAttrs[NSKernAttributeName] = vertical ? @(0.1 * labelFontSize.doubleValue) : @(0.0);
+ commentAttrs[NSKernAttributeName] = vertical ? @(0.1 * commentFontSize.doubleValue) : @(0.0);
NSFont* zhFont = CFBridgingRelease(CTFontCreateUIFontForLanguage
(kCTFontUIFontSystem, fontSize.doubleValue, (CFStringRef)scriptVariant));
@@ -1509,7 +1514,7 @@ - (void)updateWithConfig:(SquirrelConfig*)config
labelAttrs[(id)kCTBaselineReferenceInfoAttributeName] = baselineRefInfo;
commentAttrs[(id)kCTBaselineReferenceInfoAttributeName] = baselineRefInfo;
preeditAttrs[(id)kCTBaselineReferenceInfoAttributeName] = @{(id)kCTBaselineReferenceFont : zhFont};
- pagingAttrs[(id)kCTBaselineReferenceInfoAttributeName] = @{(id)kCTBaselineReferenceFont : pagingFont};
+ pagingAttrs[(id)kCTBaselineReferenceInfoAttributeName] = @{(id)kCTBaselineReferenceFont : systemFont};
statusAttrs[(id)kCTBaselineReferenceInfoAttributeName] = @{(id)kCTBaselineReferenceFont : zhCommentFont};
textAttrs[(id)kCTBaselineClassAttributeName] =
@@ -1521,7 +1526,7 @@ - (void)updateWithConfig:(SquirrelConfig*)config
vertical ? (id)kCTBaselineClassIdeographicCentered : (id)kCTBaselineClassRoman;
statusAttrs[(id)kCTBaselineClassAttributeName] =
vertical ? (id)kCTBaselineClassIdeographicCentered : (id)kCTBaselineClassRoman;
- pagingAttrs[(id)kCTBaselineClassAttributeName] = (id)kCTBaselineClassIdeographicCentered;
+ pagingAttrs[(id)kCTBaselineClassAttributeName] = (id)kCTBaselineClassRoman;
textAttrs[(id)kCTLanguageAttributeName] = scriptVariant;
labelAttrs[(id)kCTLanguageAttributeName] = scriptVariant;
@@ -1539,15 +1544,12 @@ - (void)updateWithConfig:(SquirrelConfig*)config
preeditAttrs[NSParagraphStyleAttributeName] = preeditParagraphStyle;
pagingAttrs[NSParagraphStyleAttributeName] = pagingParagraphStyle;
statusAttrs[NSParagraphStyleAttributeName] = statusParagraphStyle;
-
- labelAttrs[NSVerticalGlyphFormAttributeName] = @(vertical);
pagingAttrs[NSVerticalGlyphFormAttributeName] = @NO;
/*** CHROMATICS refinement ***/
if (@available(macOS 10.14, *)) {
- if (translucency.floatValue > 0.001f && !isNative && backColor != nil &&
- (_style == kDarkStyle ? backColor.lStarComponent > 0.6
- : backColor.lStarComponent < 0.4)) {
+ if (isnormal(translucency.floatValue) && !isNative && backColor != nil &&
+ (_style == kDarkStyle ? backColor.lStarComponent > 0.6 : backColor.lStarComponent < 0.4)) {
backColor = [backColor colorByInvertingLuminanceToExtent:kStandardColorInversion];
borderColor = [borderColor colorByInvertingLuminanceToExtent:kStandardColorInversion];
preeditBackColor = [preeditBackColor colorByInvertingLuminanceToExtent:kStandardColorInversion];
@@ -1595,7 +1597,7 @@ - (void)updateWithConfig:(SquirrelConfig*)config
_lineSpacing = lineSpacing.doubleValue;
_preeditSpacing = spacing.doubleValue;
_opacity = opacity ? opacity.doubleValue : 1.0;
- _lineLength = lineLength.doubleValue > 0.1 ? fmax(ceil(lineLength.doubleValue), fullWidth * 5) : 0.0;
+ _lineLength = isnormal(lineLength.doubleValue) ? fmax(ceil(lineLength.doubleValue), fullWidth * 5) : 0.0;
_shadowSize = shadowSize.doubleValue;
_translucency = translucency.floatValue;
_stackColors = stackColors.boolValue;
@@ -1636,14 +1638,16 @@ - (void)updateWithConfig:(SquirrelConfig*)config
_hilitedLabelForeColor = hilitedLabelForeColor;
_dimmedLabelForeColor = tabular ? [labelForeColor colorWithAlphaComponent:
labelForeColor.alphaComponent * 0.2] : nil;
-
_scriptVariant = scriptVariant;
- [self setCandidateFormat:candidateFormat ? : kDefaultCandidateFormat];
- [self setStatusMessageType:statusMessageType];
+
+ [self updateStatusMessageType:statusMessageType];
+ [self updateCandidateFormat:rawCandidateFormat];
+ [self updateSeperatorAndSymbolAttrs];
+
}
- (void)setAnnotationHeight:(CGFloat)height {
- if (height > 0.1 && _lineSpacing < height * 2) {
+ if (isnormal(height) && _lineSpacing < height * 2) {
_lineSpacing = height * 2;
NSMutableParagraphStyle* candidateParagraphStyle = _candidateParagraphStyle.mutableCopy;
if (_linear) {
@@ -1688,7 +1692,7 @@ - (void)setAnnotationHeight:(CGFloat)height {
}
}
-- (void)setScriptVariant:(NSString*)scriptVariant {
+- (void)updateScriptVariant:(NSString*)scriptVariant {
if ([scriptVariant isEqualToString:_scriptVariant]) {
return;
}
@@ -1740,30 +1744,20 @@ - (void)setScriptVariant:(NSString*)scriptVariant {
_statusAttrs = statusAttrs;
NSMutableAttributedString* candidateTemplate = _candidateTemplate.mutableCopy;
- NSRange textRange = [candidateTemplate.mutableString rangeOfString:@"%@" options:NSLiteralSearch];
- NSRange labelRange = NSMakeRange(0, textRange.location);
- NSRange commentRange = NSMakeRange(NSMaxRange(textRange),
- candidateTemplate.length - NSMaxRange(textRange));
- [candidateTemplate addAttributes:labelAttrs range:labelRange];
- [candidateTemplate addAttributes:textAttrs range:textRange];
- [candidateTemplate addAttributes:commentAttrs range:commentRange];
+ NSRange templateRange = NSMakeRange(0, candidateTemplate.length);
+ [candidateTemplate addAttribute:(id)kCTBaselineReferenceInfoAttributeName value:baselineRefInfo range:templateRange];
+ [candidateTemplate addAttribute:(id)kCTLanguageAttributeName value:scriptVariant range:templateRange];
_candidateTemplate = candidateTemplate;
+
NSMutableAttributedString* candidateHilitedTemplate = candidateTemplate.mutableCopy;
- [candidateHilitedTemplate addAttribute:NSForegroundColorAttributeName
- value:_hilitedLabelForeColor
- range:labelRange];
- [candidateHilitedTemplate addAttribute:NSForegroundColorAttributeName
- value:_hilitedTextForeColor
- range:textRange];
- [candidateHilitedTemplate addAttribute:NSForegroundColorAttributeName
- value:_hilitedCommentForeColor
- range:commentRange];
+ [candidateHilitedTemplate addAttribute:(id)kCTBaselineReferenceInfoAttributeName value:baselineRefInfo range:templateRange];
+ [candidateHilitedTemplate addAttribute:(id)kCTLanguageAttributeName value:scriptVariant range:templateRange];
_candidateHilitedTemplate = candidateHilitedTemplate;
+
if (_tabular) {
NSMutableAttributedString* candidateDimmedTemplate = candidateTemplate.mutableCopy;
- [candidateDimmedTemplate addAttribute:NSForegroundColorAttributeName
- value:_dimmedLabelForeColor
- range:labelRange];
+ [candidateDimmedTemplate addAttribute:(id)kCTBaselineReferenceInfoAttributeName value:baselineRefInfo range:templateRange];
+ [candidateDimmedTemplate addAttribute:(id)kCTLanguageAttributeName value:scriptVariant range:templateRange];
_candidateDimmedTemplate = candidateDimmedTemplate;
}
}
@@ -1773,14 +1767,6 @@ - (void)setScriptVariant:(NSString*)scriptVariant {
#pragma mark - Auxiliary structs and views
-typedef NS_CLOSED_ENUM(NSUInteger, SquirrelContentBlock) {
- kPreeditBlock,
- kLinearCandidatesBlock,
- kStackedCandidatesBlock,
- kPagingBlock,
- kStatusBlock
-};
-
typedef struct SquirrelTabularIndex {
NSUInteger index;
NSUInteger lineNum;
@@ -1814,41 +1800,15 @@ inline NSRange commentRange() {
}
} SquirrelCandidateInfo;
-__attribute__((objc_direct_members))
-@interface NSFlippedView : NSView
-@end
-
-__attribute__((objc_direct_members))
-@interface SquirrelTextView : NSTextView
-
-@property(nonatomic) SquirrelContentBlock contentBlock;
-
-- (instancetype)initWithContentBlock:(SquirrelContentBlock)contentBlock
- storage:(NSTextStorage*)textStorage;
-- (NSTextRange*)textRangeFromCharRange:(NSRange)charRange API_AVAILABLE(macos(12.0));
-- (NSRange)charRangeFromTextRange:(NSTextRange*)textRange API_AVAILABLE(macos(12.0));
-- (NSRect)layoutText;
-- (NSRect)blockRectForRange:(NSRange)charRange;
-- (SquirrelTextPolygon)textPolygonForRange:(NSRange)charRange;
-
-@end
-
#pragma mark - Typesetting extensions for TextKit 1 (Mac OSX 10.9 to MacOS 11)
__attribute__((objc_direct_members))
@interface SquirrelLayoutManager : NSLayoutManager
-
-@property(nonatomic, readonly) SquirrelContentBlock contentBlock;
-
@end
@implementation SquirrelLayoutManager
-- (SquirrelContentBlock)contentBlock {
- return ((SquirrelTextView*)self.firstTextView).contentBlock;
-}
-
- (void)drawGlyphsForGlyphRange:(NSRange)glyphsToShow
atPoint:(NSPoint)origin {
NSTextContainer* textContainer = [self textContainerForGlyphAtIndex:glyphsToShow.location
@@ -1862,60 +1822,52 @@ - (void)drawGlyphsForGlyphRange:(NSRange)glyphsToShow
NSRange charRange = [self characterRangeForGlyphRange:lineRange actualGlyphRange:NULL];
[self.textStorage enumerateAttributesInRange:charRange options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:^(NSDictionary * _Nonnull attrs, NSRange runRange, BOOL * _Nonnull stop) {
NSRange runGlyphRange = [self glyphRangeForCharacterRange:runRange actualCharacterRange:NULL];
- if (attrs[(id)kCTRubyAnnotationAttributeName] != nil) {
+ NSFont* runFont = attrs[NSFontAttributeName];
+ if (attrs[(id)kCTRubyAnnotationAttributeName] != nil || (verticalOrientation && [runFont.fontName isEqualToString:@"AppleColorEmoji"] && runFont.pointSize < 24)) {
CGContextSaveGState(context);
CGContextScaleCTM(context, 1.0, -1.0);
- NSUInteger glyphIndex = runGlyphRange.location;
- CTLineRef line = CTLineCreateWithAttributedString((CFAttributedStringRef)
- [self.textStorage attributedSubstringFromRange:runRange]);
+ CGPoint position = [self locationForGlyphAtIndex:runGlyphRange.location];
+ position.x += lineRect.origin.x + origin.x;
+ position.y += lineRect.origin.y + origin.y;
+ CTLineRef line;
+ if (attrs[(id)kCTRubyAnnotationAttributeName] == nil) {
+ NSMutableAttributedString* subString = [self.textStorage attributedSubstringFromRange:runRange].mutableCopy;
+ [subString addAttribute:NSVerticalGlyphFormAttributeName value:@1 range:NSMakeRange(0, runRange.length)];
+ line = CTLineCreateWithAttributedString((CFAttributedStringRef)subString);
+ if (NSInteger superscript = [attrs[NSSuperscriptAttributeName] integerValue] != 0) {
+ position.y -= runFont.descender + superscript * 0.5;
+ }
+ } else {
+ line = CTLineCreateWithAttributedString
+ ((CFAttributedStringRef)[self.textStorage attributedSubstringFromRange:runRange]);
+ }
CFArrayRef runs = CTLineGetGlyphRuns((CTLineRef)CFAutorelease(line));
for (CFIndex i = 0; i < CFArrayGetCount(runs); ++i) {
- CGPoint position = [self locationForGlyphAtIndex:glyphIndex];
CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, i);
- CFIndex glyphCount = CTRunGetGlyphCount(run);
CGAffineTransform matrix = CTRunGetTextMatrix(run);
- CGPoint glyphOrigin = [textContainer.textView convertPointToBacking:
- CGPointMake(origin.x + lineRect.origin.x + position.x,
- -origin.y - lineRect.origin.y - position.y)];
- glyphOrigin = [textContainer.textView convertPointFromBacking:
- CGPointMake(round(glyphOrigin.x), round(glyphOrigin.y))];
+ CGPoint glyphOrigin = CGContextConvertPointToDeviceSpace(context, position);
+ glyphOrigin = CGContextConvertPointToUserSpace(context, CGPointMake(ceil(glyphOrigin.x), ceil(glyphOrigin.y)));
matrix.tx = glyphOrigin.x;
- matrix.ty = glyphOrigin.y;
+ matrix.ty = -glyphOrigin.y;
CGContextSetTextMatrix(context, matrix);
- CTRunDraw(run, context, CFRangeMake(0, glyphCount));
- glyphIndex += (NSUInteger)glyphCount;
+ CTRunDraw(run, context, CFRangeMake(0, 0));
+ if (i < CFArrayGetCount(runs) - 1) {
+ position.x += CTRunGetTypographicBounds(run, CFRangeMake(0, 0), NULL, NULL, NULL);
+ }
}
CGContextRestoreGState(context);
} else {
- NSPoint position = [self locationForGlyphAtIndex:runGlyphRange.location];
- position.x += origin.x;
- position.y += origin.y;
- NSFont* runFont = attrs[NSFontAttributeName];
- NSString* baselineClass = attrs[(id)kCTBaselineClassAttributeName];
- NSPoint offset = NSZeroPoint;
- if (!verticalOrientation &&
- ([baselineClass isEqualToString:(id)kCTBaselineClassIdeographicCentered] ||
- [baselineClass isEqualToString:(id)kCTBaselineClassMath])) {
+ NSPoint glyphOrigin = origin;
+ if (!verticalOrientation) {
NSFont* refFont = attrs[(id)kCTBaselineReferenceInfoAttributeName][(id)kCTBaselineReferenceFont];
- offset.y += (runFont.ascender + runFont.descender - refFont.ascender - refFont.descender) * 0.5;
- } else if (verticalOrientation && runFont.pointSize < 24 &&
- [runFont.fontName isEqualToString:@"AppleColorEmoji"]) {
- NSInteger superscript = [attrs[NSSuperscriptAttributeName] integerValue];
- offset.x += runFont.capHeight - runFont.pointSize;
- offset.y += (runFont.capHeight - runFont.pointSize) *
- (superscript == 0 ? 0.25 : (superscript == 1 ? 0.5 / 0.55 : 0.0));
+ glyphOrigin.y += (runFont.ascender + runFont.descender - refFont.ascender - refFont.descender) * 0.5;
}
- NSPoint glyphOrigin = [textContainer.textView convertPointToBacking:
- NSMakePoint(position.x + offset.x, position.y + offset.y)];
- glyphOrigin = [textContainer.textView convertPointFromBacking:
- NSMakePoint(round(glyphOrigin.x), round(glyphOrigin.y))];
- [super drawGlyphsForGlyphRange:runGlyphRange
- atPoint:NSMakePoint(glyphOrigin.x - position.x,
- glyphOrigin.y - position.y)];
+ glyphOrigin = CGContextConvertPointToDeviceSpace(context, glyphOrigin);
+ glyphOrigin = CGContextConvertPointToUserSpace(context, NSMakePoint(ceil(glyphOrigin.x), ceil(glyphOrigin.y)));
+ [super drawGlyphsForGlyphRange:runGlyphRange atPoint:glyphOrigin];
}
}];
}];
- CGContextClipToRect(context, textContainer.textView.superview.bounds);
}
- (BOOL) layoutManager:(NSLayoutManager*)layoutManager
@@ -1924,12 +1876,14 @@ - (BOOL) layoutManager:(NSLayoutManager*)layoutManager
baselineOffset:(inout CGFloat*)baselineOffset
inTextContainer:(NSTextContainer*)textContainer
forGlyphRange:(NSRange)glyphRange {
+ NSParagraphStyle* rulerAttrs = textContainer.textView.defaultParagraphStyle;
+ if (rulerAttrs == nil) {
+ return NO;
+ }
BOOL didModify = NO;
BOOL verticalOrientation = textContainer.layoutOrientation == NSTextLayoutOrientationVertical;
NSRange charRange = [layoutManager characterRangeForGlyphRange:glyphRange
actualGlyphRange:NULL];
- NSParagraphStyle* rulerAttrs = textContainer.textView.defaultParagraphStyle;
- CGFloat lineSpacing = rulerAttrs.lineSpacing;
CGFloat lineHeight = rulerAttrs.minimumLineHeight;
CGFloat baseline = lineHeight * 0.5;
if (!verticalOrientation) {
@@ -1939,16 +1893,10 @@ - (BOOL) layoutManager:(NSLayoutManager*)layoutManager
effectiveRange:NULL][(id)kCTBaselineReferenceFont];
baseline += (refFont.ascender + refFont.descender) * 0.5;
}
- CGFloat lineHeightDelta = lineFragmentUsedRect->size.height - lineHeight - lineSpacing;
- if (fabs(lineHeightDelta) > 0.1) {
- lineFragmentUsedRect->size.height = round(lineFragmentUsedRect->size.height - lineHeightDelta);
- lineFragmentRect->size.height = round(lineFragmentRect->size.height - lineHeightDelta);
- didModify |= YES;
- }
- CGFloat newBaselineOffset = floor(lineFragmentUsedRect->origin.y - lineFragmentRect->origin.y + baseline);
- if (fabs(*baselineOffset - newBaselineOffset) > 0.1) {
+ CGFloat newBaselineOffset = round(lineFragmentUsedRect->origin.y - lineFragmentRect->origin.y + baseline);
+ if (isnormal(*baselineOffset - newBaselineOffset)) {
*baselineOffset = newBaselineOffset;
- didModify |= YES;
+ didModify = YES;
}
return didModify;
}
@@ -1960,8 +1908,9 @@ - (BOOL) layoutManager:(NSLayoutManager*)layoutManager
} else {
unichar charBeforeIndex = [layoutManager.textStorage.mutableString
characterAtIndex:charIndex - 1];
- return self.contentBlock == kLinearCandidatesBlock ? charBeforeIndex == 0x1D
- : charBeforeIndex != '\t';
+ SquirrelTextView* textView = (SquirrelTextView*)layoutManager.firstTextView;
+ return textView.contentBlock == kLinearCandidatesBlock ? charBeforeIndex == 0x1D
+ : charBeforeIndex != '\t';
}
}
@@ -1985,24 +1934,13 @@ - (NSRect) layoutManager:(NSLayoutManager*)layoutManager
proposedLineFragment:(NSRect)proposedRect
glyphPosition:(NSPoint)glyphPosition
characterIndex:(NSUInteger)charIndex {
- CGFloat width = 0.0;
- if (charIndex > 0 && [layoutManager.textStorage.mutableString
- characterAtIndex:charIndex] == 0x8B) {
- NSRange rubyRange;
- id rubyAnnotation =
- [layoutManager.textStorage attribute:(id)kCTRubyAnnotationAttributeName
- atIndex:charIndex - 1
- effectiveRange:&rubyRange];
- if (rubyAnnotation != nil) {
- NSAttributedString* rubyString = [layoutManager.textStorage
- attributedSubstringFromRange:rubyRange];
- CTLineRef line = CTLineCreateWithAttributedString((CFAttributedStringRef)rubyString);
- CGRect rubyRect = CTLineGetBoundsWithOptions((CTLineRef)CFAutorelease(line), 0);
- width = fdim(rubyRect.size.width, rubyString.size.width);
+ NSRect rect = {glyphPosition, NSZeroSize};
+ if ([layoutManager.textStorage.mutableString characterAtIndex:charIndex] == 0x8B) {
+ if (NSValue* controlCharacterSize = [layoutManager.textStorage attribute:kControlCharacterSizeAttributeName atIndex:charIndex effectiveRange:NULL]) {
+ rect.size = controlCharacterSize.sizeValue;
}
}
- return NSMakeRect(glyphPosition.x, glyphPosition.y, width,
- NSMaxY(proposedRect) - glyphPosition.y);
+ return rect;
}
@end // SquirrelLayoutManager
@@ -2011,11 +1949,36 @@ - (NSRect) layoutManager:(NSLayoutManager*)layoutManager
#pragma mark - Typesetting extensions for TextKit 2 (MacOS 12 or higher)
API_AVAILABLE(macos(12.0))
-@interface SquirrelTextLayoutFragment : NSTextLayoutFragment
+@interface SquirrelTextLayoutFragment : NSTextLayoutFragment
@end
@implementation SquirrelTextLayoutFragment
+- (NSTextLayoutOrientation)layoutOrientation {
+ return self.textLayoutManager.textContainer.layoutOrientation;
+}
+
+- (CGRect)renderingSurfaceBounds {
+ CGRect bounds = super.renderingSurfaceBounds;
+ if (self.state == NSTextLayoutFragmentStateLayoutAvailable) {
+ SquirrelContentBlock contentBlock = ((SquirrelTextView*)self.textLayoutManager.textContainer.textView).contentBlock;
+ if (contentBlock == kLinearCandidatesBlock || contentBlock == kStackedCandidatesBlock) {
+ NSParagraphStyle* rulerStyle = self.textLayoutManager.textContainer.textView.defaultParagraphStyle;
+ if ([self.rangeInElement.location isEqual:self.textLayoutManager.documentRange.location]) {
+ CGFloat spacing = contentBlock == kStackedCandidatesBlock ?
+ rulerStyle.paragraphSpacingBefore : floor(rulerStyle.lineSpacing * 0.5);
+ bounds.origin.y -= spacing;
+ bounds.size.height += spacing;
+ }
+ if ([self.rangeInElement.endLocation isEqual:self.textLayoutManager.documentRange.endLocation]) {
+ bounds.size.height += contentBlock == kStackedCandidatesBlock ?
+ rulerStyle.paragraphSpacing : ceil(rulerStyle.lineSpacing * 0.5);
+ }
+ }
+ }
+ return bounds;
+}
+
- (void)drawAtPoint:(CGPoint)point
inContext:(CGContextRef)context {
if (@available(macOS 14.0, *)) {
@@ -2023,11 +1986,10 @@ - (void)drawAtPoint:(CGPoint)point
point.x -= self.layoutFragmentFrame.origin.x;
point.y -= self.layoutFragmentFrame.origin.y;
}
- BOOL verticalOrientation = self.textLayoutManager.textContainer.layoutOrientation == NSTextLayoutOrientationVertical;
for (NSTextLineFragment* lineFrag in self.textLineFragments) {
CGRect lineRect = CGRectOffset(lineFrag.typographicBounds, point.x, point.y);
CGFloat baseline = CGRectGetMidY(lineRect);
- if (!verticalOrientation) {
+ if (self.layoutOrientation == NSTextLayoutOrientationHorizontal) {
NSFont* refFont = [lineFrag.attributedString
attribute:(id)kCTBaselineReferenceInfoAttributeName
atIndex:lineFrag.characterRange.location
@@ -2035,10 +1997,10 @@ - (void)drawAtPoint:(CGPoint)point
baseline += (refFont.ascender + refFont.descender) * 0.5;
}
CGPoint renderOrigin = CGPointMake(NSMinX(lineRect) + lineFrag.glyphOrigin.x,
- floor(baseline) - lineFrag.glyphOrigin.y);
- CGPoint deviceOrigin = CGContextConvertPointToDeviceSpace(context, renderOrigin);
- renderOrigin = CGContextConvertPointToUserSpace(context,
- CGPointMake(round(deviceOrigin.x), round(deviceOrigin.y)));
+ round(baseline) - lineFrag.glyphOrigin.y);
+ renderOrigin = CGContextConvertPointToDeviceSpace(context, renderOrigin);
+ renderOrigin = CGContextConvertPointToUserSpace(context, CGPointMake(ceil(renderOrigin.x),
+ ceil(renderOrigin.y)));
[lineFrag drawAtPoint:renderOrigin inContext:context];
}
}
@@ -2048,17 +2010,10 @@ - (void)drawAtPoint:(CGPoint)point
__attribute__((objc_direct_members)) API_AVAILABLE(macos(12.0))
@interface SquirrelTextLayoutManager : NSTextLayoutManager
-
-@property(nonatomic, readonly) SquirrelContentBlock contentBlock;
-
@end
@implementation SquirrelTextLayoutManager
-- (SquirrelContentBlock)contentBlock {
- return ((SquirrelTextView*)self.textContainer.textView).contentBlock;
-}
-
- (BOOL) textLayoutManager:(NSTextLayoutManager*)textLayoutManager
shouldBreakLineBeforeLocation:(id)location
hyphenating:(BOOL)hyphenating {
@@ -2071,18 +2026,20 @@ - (BOOL) textLayoutManager:(NSTextLayoutManager*)textLayoutManager
} else {
unichar charBeforeIndex = [contentStorage.textStorage.mutableString
characterAtIndex:charIndex - 1];
- return self.contentBlock == kLinearCandidatesBlock ? charBeforeIndex == 0x1D
- : charBeforeIndex != '\t';
+ SquirrelTextView* textView = (SquirrelTextView*)textLayoutManager.textContainer.textView;
+ return textView.contentBlock == kLinearCandidatesBlock ? charBeforeIndex == 0x1D
+ : charBeforeIndex != '\t';
}
}
- (NSTextLayoutFragment*)textLayoutManager:(NSTextLayoutManager*)textLayoutManager
textLayoutFragmentForLocation:(id)location
inTextElement:(NSTextElement*)textElement {
- NSTextRange* textRange = [NSTextRange.alloc initWithLocation:location
- endLocation:textElement.elementRange.endLocation];
- return [SquirrelTextLayoutFragment.alloc
- initWithTextElement:textElement range:textRange];
+ NSTextRange* textRange = [NSTextRange.alloc
+ initWithLocation:location
+ endLocation:textElement.elementRange.endLocation];
+ return [SquirrelTextLayoutFragment.alloc initWithTextElement:textElement
+ range:textRange];
}
@end // SquirrelTextLayoutManager
@@ -2190,7 +2147,7 @@ - (NSRect)blockRectForRange:(NSRange)charRange {
usingBlock:^BOOL(NSTextRange* _Nullable segRange, CGRect segFrame,
CGFloat baseline, NSTextContainer* _Nonnull textContainer) {
if (!CGRectIsEmpty(segFrame)) {
- if (NSIsEmptyRect(firstLineRect) || CGRectGetMinY(segFrame) < NSMaxY(firstLineRect) - 0.1) {
+ if (NSIsEmptyRect(firstLineRect) || CGRectGetMinY(segFrame) < nexttoward(NSMaxY(firstLineRect), -INFINITY)) {
firstLineRect = NSUnionRect(segFrame, firstLineRect);
} else {
finalLineRect = NSUnionRect(segFrame, finalLineRect);
@@ -2199,13 +2156,12 @@ - (NSRect)blockRectForRange:(NSRange)charRange {
return YES;
}];
- if (_contentBlock == kLinearCandidatesBlock && self.defaultParagraphStyle.lineSpacing > 0.1) {
+ if (_contentBlock == kLinearCandidatesBlock && isnormal(self.defaultParagraphStyle.lineSpacing)) {
firstLineRect.size.height += self.defaultParagraphStyle.lineSpacing;
if (!NSIsEmptyRect(finalLineRect))
finalLineRect.size.height += self.defaultParagraphStyle.lineSpacing;
}
-
if (NSIsEmptyRect(finalLineRect)) {
return firstLineRect;
} else {
@@ -2217,29 +2173,37 @@ - (NSRect)blockRectForRange:(NSRange)charRange {
NSRange glyphRange = [self.layoutManager glyphRangeForCharacterRange:charRange
actualCharacterRange:NULL];
NSRange firstLineRange = NSMakeRange(NSNotFound, 0);
- NSRect firstLineRect = [self.layoutManager
- lineFragmentUsedRectForGlyphAtIndex:glyphRange.location
- effectiveRange:&firstLineRange];
+ NSRect firstLineRect = [self.layoutManager lineFragmentUsedRectForGlyphAtIndex:glyphRange.location
+ effectiveRange:&firstLineRange];
if (NSMaxRange(glyphRange) <= NSMaxRange(firstLineRange)) {
CGFloat leading = [self.layoutManager locationForGlyphAtIndex:glyphRange.location].x;
- CGFloat trailing = NSMaxRange(glyphRange) < NSMaxRange(firstLineRange)
- ? [self.layoutManager locationForGlyphAtIndex:NSMaxRange(glyphRange)].x
- : NSMaxX(firstLineRect);
- return NSMakeRect(NSMinX(firstLineRect) + leading, NSMinY(firstLineRect),
- trailing - leading, NSHeight(firstLineRect));
+ CGFloat trailing = NSMaxRange(glyphRange) < NSMaxRange(firstLineRange) ?
+ [self.layoutManager locationForGlyphAtIndex:NSMaxRange(glyphRange)].x : NSMaxX(firstLineRect);
+ CGFloat height = NSHeight(firstLineRect);
+ if (self.contentBlock == kLinearCandidatesBlock &&
+ NSMaxRange(firstLineRange) == self.layoutManager.numberOfGlyphs &&
+ isnormal(self.defaultParagraphStyle.lineSpacing)) {
+ height += self.defaultParagraphStyle.lineSpacing;
+ }
+ return NSMakeRect(NSMinX(firstLineRect) + leading, NSMinY(firstLineRect), trailing - leading, height);
} else {
- NSRect finalLineRect = [self.layoutManager
- lineFragmentUsedRectForGlyphAtIndex:NSMaxRange(glyphRange) - 1
- effectiveRange:NULL];
+ NSRange finalLineRange = NSMakeRange(NSNotFound, 0);
+ NSRect finalLineRect = [self.layoutManager lineFragmentUsedRectForGlyphAtIndex:NSMaxRange(glyphRange) - 1
+ effectiveRange:&finalLineRange];
CGFloat containerWidth = NSWidth([self.layoutManager usedRectForTextContainer:self.textContainer]);
- return NSMakeRect(0.0, NSMinY(firstLineRect), containerWidth,
- NSMaxY(finalLineRect) - NSMinY(firstLineRect));
+ CGFloat height = NSMaxY(finalLineRect) - NSMinY(firstLineRect);
+ if (self.contentBlock == kLinearCandidatesBlock &&
+ NSMaxRange(finalLineRange) == self.layoutManager.numberOfGlyphs &&
+ isnormal(self.defaultParagraphStyle.lineSpacing)) {
+ height += self.defaultParagraphStyle.lineSpacing;
+ }
+ return NSMakeRect(0.0, NSMinY(firstLineRect), containerWidth, height);
}
}
}
-/* Calculate 3 rectangles encloding the text in range. TextPolygon.head & .tail are incomplete line fragments
- TextPolygon.body is the complete line fragment in the middle if the range spans no less than one full line */
+/** Calculate 3 rectangles enclosing the text in range. `textPolygon.head` & `.tail` are incomplete line fragments
+ `textPolygon.body` is the complete line fragment in the middle if the range spans no less than one full line */
- (SquirrelTextPolygon)textPolygonForRange:(NSRange)charRange {
SquirrelTextPolygon textPolygon =
{.head = NSZeroRect, .body = NSZeroRect, .tail = NSZeroRect};
@@ -2259,7 +2223,7 @@ - (SquirrelTextPolygon)textPolygonForRange:(NSRange)charRange {
usingBlock:^BOOL(NSTextRange* _Nullable segRange, CGRect segFrame,
CGFloat baseline, NSTextContainer* _Nonnull textContainer) {
if (!CGRectIsEmpty(segFrame)) {
- if (NSIsEmptyRect(headLineRect) || CGRectGetMinY(segFrame) < NSMaxY(headLineRect) - 0.1) {
+ if (NSIsEmptyRect(headLineRect) || CGRectGetMinY(segFrame) < nexttoward(NSMaxY(headLineRect), -INFINITY)) {
headLineRect = NSUnionRect(segFrame, headLineRect);
headLineRange = [headLineRange textRangeByFormingUnionWithTextRange:segRange];
} else {
@@ -2269,7 +2233,7 @@ - (SquirrelTextPolygon)textPolygonForRange:(NSRange)charRange {
}
return YES;
}];
- if (_contentBlock == kLinearCandidatesBlock && self.defaultParagraphStyle.lineSpacing > 0.1) {
+ if (_contentBlock == kLinearCandidatesBlock && isnormal(self.defaultParagraphStyle.lineSpacing)) {
headLineRect.size.height += self.defaultParagraphStyle.lineSpacing;
if (!NSIsEmptyRect(tailLineRect))
tailLineRect.size.height += self.defaultParagraphStyle.lineSpacing;
@@ -2305,25 +2269,31 @@ - (SquirrelTextPolygon)textPolygonForRange:(NSRange)charRange {
NSRange glyphRange = [self.layoutManager glyphRangeForCharacterRange:charRange
actualCharacterRange:NULL];
NSRange headLineRange = NSMakeRange(NSNotFound, 0);
- NSRect headLineRect = [self.layoutManager
- lineFragmentUsedRectForGlyphAtIndex:glyphRange.location
- effectiveRange:&headLineRange];
+ NSRect headLineRect = [self.layoutManager lineFragmentUsedRectForGlyphAtIndex:glyphRange.location
+ effectiveRange:&headLineRange];
CGFloat leading = [self.layoutManager locationForGlyphAtIndex:glyphRange.location].x;
if (NSMaxRange(headLineRange) >= NSMaxRange(glyphRange)) {
- CGFloat trailing = NSMaxRange(glyphRange) < NSMaxRange(headLineRange)
- ? [self.layoutManager locationForGlyphAtIndex:NSMaxRange(glyphRange)].x
- : NSMaxX(headLineRect);
- textPolygon.body = NSMakeRect(leading, NSMinY(headLineRect),
- trailing - leading, NSHeight(headLineRect));
+ CGFloat trailing = NSMaxRange(glyphRange) < NSMaxRange(headLineRange) ?
+ [self.layoutManager locationForGlyphAtIndex:NSMaxRange(glyphRange)].x : NSMaxX(headLineRect);
+ CGFloat height = NSHeight(headLineRect);
+ if (self.contentBlock == kLinearCandidatesBlock &&
+ NSMaxRange(headLineRange) == self.layoutManager.numberOfGlyphs &&
+ isnormal(self.defaultParagraphStyle.lineSpacing)) {
+ height += self.defaultParagraphStyle.lineSpacing;
+ }
+ textPolygon.body = NSMakeRect(leading, NSMinY(headLineRect), trailing - leading, height);
} else {
CGFloat containerWidth = NSWidth([self.layoutManager usedRectForTextContainer:self.textContainer]);
NSRange tailLineRange = NSMakeRange(NSNotFound, 0);
- NSRect tailLineRect = [self.layoutManager
- lineFragmentUsedRectForGlyphAtIndex:NSMaxRange(glyphRange) - 1
- effectiveRange:&tailLineRange];
- CGFloat trailing = NSMaxRange(glyphRange) < NSMaxRange(tailLineRange)
- ? [self.layoutManager locationForGlyphAtIndex:NSMaxRange(glyphRange)].x
- : NSMaxX(tailLineRect);
+ NSRect tailLineRect = [self.layoutManager lineFragmentUsedRectForGlyphAtIndex:NSMaxRange(glyphRange) - 1
+ effectiveRange:&tailLineRange];
+ if (self.contentBlock == kLinearCandidatesBlock &&
+ NSMaxRange(tailLineRange) == self.layoutManager.numberOfGlyphs &&
+ isnormal(self.defaultParagraphStyle.lineSpacing)) {
+ tailLineRect.size.height += self.defaultParagraphStyle.lineSpacing;
+ }
+ CGFloat trailing = NSMaxRange(glyphRange) < NSMaxRange(tailLineRange) ?
+ [self.layoutManager locationForGlyphAtIndex:NSMaxRange(glyphRange)].x : NSMaxX(tailLineRect);
if (NSMaxRange(tailLineRange) == NSMaxRange(glyphRange)) {
if (glyphRange.location == headLineRange.location) {
textPolygon.body = NSMakeRect(0.0, NSMinY(headLineRect), containerWidth,
@@ -2475,8 +2445,9 @@ - (instancetype)init {
_scrollView.hasVerticalScroller = YES;
_scrollView.scrollerStyle = NSScrollerStyleOverlay;
_scrollView.scrollerKnobStyle = NSScrollerKnobStyleDark;
- _scrollView.contentView.wantsLayer = YES;
- _scrollView.contentView.layer.geometryFlipped = YES;
+ _scrollView.wantsLayer = YES;
+ _scrollView.layer.geometryFlipped = YES;
+ _scrollView.layerContentsRedrawPolicy = NSViewLayerContentsRedrawOnSetNeedsDisplay;
_style = kDefaultStyle;
_theme = _defaultTheme;
@@ -2520,7 +2491,7 @@ - (instancetype)init {
[_documentView.layer addSublayer:_gridLayer];
[_documentView.layer addSublayer:_nonHilitedCandidateLayer];
[_documentView.layer addSublayer:_hilitedCandidateLayer];
- _scrollView.contentView.layer.mask = _clipLayer;
+ _scrollView.layer.mask = _clipLayer;
}
return self;
}
@@ -2547,7 +2518,7 @@ - (void)updateColors {
}
if (_theme.hilitedCandidateBackColor != nil) {
_hilitedCandidateLayer.fillColor = _theme.hilitedCandidateBackColor.CGColor;
- if (_theme.shadowSize > 0.1) {
+ if (isnormal(_theme.shadowSize)) {
_hilitedCandidateLayer.shadowOffset = CGSizeMake(_theme.shadowSize, _theme.shadowSize);
_hilitedCandidateLayer.shadowOpacity = 1.0;
} else {
@@ -2574,8 +2545,7 @@ - (void)updateColors {
static BOOL anyTruncated(SquirrelCandidateInfo* array, NSUInteger count) {
for (NSUInteger i = 0; i < count; ++i) {
- if (array[i].truncated)
- return YES;
+ if (array[i].truncated) return YES;
}
return NO;
}
@@ -2608,12 +2578,7 @@ - (void)estimateBoundsOnScreen:(NSRect)screen
}
if (candidateCount > 0) {
_documentRect = _candidateView.layoutText;
- if (@available(macOS 12.0, *)) {
- _documentRect.size.height += _theme.lineSpacing;
- } else {
- _documentRect.size.height += _theme.linear ? 0.0 : _theme.lineSpacing;
- }
-
+ _documentRect.size.height += _theme.lineSpacing;
if (_theme.linear && !anyTruncated(candidateInfos, candidateCount)) {
_documentRect.size.width -= _theme.fullWidth;
}
@@ -2647,7 +2612,8 @@ - (void)layoutContents {
NSPoint origin = NSMakePoint(_theme.borderInsets.width,
_theme.borderInsets.height);
if (!_statusView.hidden) { // status
- _contentRect.origin = NSMakePoint(origin.x + ceil(_theme.fullWidth * 0.5), origin.y);
+ _contentRect.origin = NSMakePoint(origin.x + ceil(_theme.fullWidth * 0.5),
+ origin.y);
return;
}
if (!_preeditView.hidden) {
@@ -2657,8 +2623,6 @@ - (void)layoutContents {
_contentRect = _preeditRect;
}
if (!_scrollView.hidden) {
- _clipRect.size.width = NSWidth(_documentRect);
- _clipRect.size.height = NSHeight(_documentRect) - _clippedHeight;
if (!_preeditView.hidden) {
_clipRect.origin.x = origin.x;
_clipRect.origin.y = NSMaxY(_preeditRect) + _theme.preeditSpacing;
@@ -2693,8 +2657,8 @@ - (void)drawViewWithHilitedCandidate:(NSUInteger)hilitedCandidate
_preeditView.needsDisplayInRect = _preeditView.bounds;
// invalidate Rect beyond bound of textview to clear any out-of-bound drawing from last round
if (!_scrollView.hidden)
- _candidateView.needsDisplayInRect = [_candidateView convertRect:_documentView.bounds
- fromView:_documentView];
+ _candidateView.needsDisplayInRect = [_documentView convertRect:_documentView.bounds
+ toView:_candidateView];
if (!_pagingView.hidden)
_pagingView.needsDisplayInRect = _pagingView.bounds;
}
@@ -2713,40 +2677,48 @@ - (void)highlightCandidate:(NSUInteger)hilitedCandidate {
NSUInteger priorActivePage = _hilitedCandidate / _theme.pageSize;
NSUInteger newActivePage = hilitedCandidate / _theme.pageSize;
if (newActivePage != priorActivePage) {
- self.needsDisplayInRect = [_documentView convertRect:_sectionRects[priorActivePage] toView:self];
- _candidateView.needsDisplayInRect = [_documentView convertRect:_sectionRects[priorActivePage] toView:_candidateView];
+ self.needsDisplayInRect = [_documentView convertRect:_sectionRects[priorActivePage]
+ toView:self];
+ _candidateView.needsDisplayInRect = [_documentView convertRect:_sectionRects[priorActivePage]
+ toView:_candidateView];
}
- self.needsDisplayInRect = [_documentView convertRect:_sectionRects[newActivePage] toView:self];
- _candidateView.needsDisplayInRect = [_documentView convertRect:_sectionRects[newActivePage] toView:_candidateView];
+ self.needsDisplayInRect = [_documentView convertRect:_sectionRects[newActivePage]
+ toView:self];
+ _candidateView.needsDisplayInRect = [_documentView convertRect:_sectionRects[newActivePage]
+ toView:_candidateView];
} else {
self.needsDisplayInRect = _clipRect;
- _candidateView.needsDisplayInRect = [_documentView convertRect:_documentView.bounds toView:_candidateView];
+ _candidateView.needsDisplayInRect = [_documentView convertRect:_documentView.bounds
+ toView:_candidateView];
}
_hilitedCandidate = hilitedCandidate;
[self unclipHighlightedCandidate];
}
- (void)unclipHighlightedCandidate {
+ if (!isnormal(_clippedHeight)) {
+ return;
+ }
if (_expanded) {
NSUInteger activePage = _hilitedCandidate / _theme.pageSize;
- if (NSMinY(_sectionRects[activePage]) < NSMinY(_scrollView.documentVisibleRect) - 0.1) {
+ if (NSMinY(_sectionRects[activePage]) < nexttoward(NSMinY(_scrollView.documentVisibleRect), -INFINITY)) {
NSPoint origin = _scrollView.contentView.bounds.origin;
origin.y -= NSMinY(_scrollView.documentVisibleRect) - NSMinY(_sectionRects[activePage]);
[_scrollView.contentView scrollToPoint:origin];
_scrollView.verticalScroller.doubleValue = NSMinY(_scrollView.documentVisibleRect) / _clippedHeight;
- } else if (NSMaxY(_sectionRects[activePage]) > NSMaxY(_scrollView.documentVisibleRect) + 0.1) {
+ } else if (NSMaxY(_sectionRects[activePage]) > nexttoward(NSMaxY(_scrollView.documentVisibleRect), INFINITY)) {
NSPoint origin = _scrollView.contentView.bounds.origin;
origin.y += NSMaxY(_sectionRects[activePage]) - NSMaxY(_scrollView.documentVisibleRect);
[_scrollView.contentView scrollToPoint:origin];
_scrollView.verticalScroller.doubleValue = NSMinY(_scrollView.documentVisibleRect) / _clippedHeight;
}
} else {
- if (NSMinY(_scrollView.documentVisibleRect) > _candidatePolygons[_hilitedCandidate].minY() + 0.1) {
+ if (NSMinY(_scrollView.documentVisibleRect) > nexttoward(_candidatePolygons[_hilitedCandidate].minY(), INFINITY)) {
NSPoint origin = _scrollView.contentView.bounds.origin;
origin.y -= NSMinY(_scrollView.documentVisibleRect) - _candidatePolygons[_hilitedCandidate].minY();
[_scrollView.contentView scrollToPoint:origin];
_scrollView.verticalScroller.doubleValue = NSMinY(_scrollView.documentVisibleRect) / _clippedHeight;
- } else if (NSMaxY(_scrollView.documentVisibleRect) < _candidatePolygons[_hilitedCandidate].maxY() - 0.1) {
+ } else if (NSMaxY(_scrollView.documentVisibleRect) < nexttoward(_candidatePolygons[_hilitedCandidate].maxY(), -INFINITY)) {
NSPoint origin = _scrollView.contentView.bounds.origin;
origin.y += _candidatePolygons[_hilitedCandidate].maxY() - NSMaxY(_scrollView.documentVisibleRect);
[_scrollView.contentView scrollToPoint:origin];
@@ -2832,6 +2804,12 @@ - (NSBezierPath*)updateFunctionButtonLayer {
NSBezierPath* buttonPath = [NSBezierPath squirclePathForRect:buttonRect cornerRadius:cornerRadius];
_functionButtonLayer.path = buttonPath.quartzPath;
_functionButtonLayer.fillColor = buttonColor.CGColor;
+ if (isnormal(_theme.shadowSize)) {
+ _functionButtonLayer.shadowOffset = CGSizeMake(_theme.shadowSize, _theme.shadowSize);
+ _functionButtonLayer.shadowOpacity = 1.0;
+ } else {
+ _functionButtonLayer.shadowOpacity = 0.0;
+ }
_functionButtonLayer.hidden = NO;
return buttonPath;
} else {
@@ -2849,10 +2827,11 @@ - (void)updateLayer {
CGFloat hilitedCornerRadius = fmin(_theme.hilitedCornerRadius,
_theme.candidateParagraphStyle.minimumLineHeight * 0.5);
- /*** Preedit Rects **/
+ /* Preedit */
_deleteBackRect = NSZeroRect;
NSBezierPath* hilitedPreeditPath;
if (!_preeditView.hidden) {
+ _preeditRect.origin = backgroundRect.origin;
_preeditRect.size.width = NSWidth(backgroundRect);
_preeditRect = [self backingAlignedRect:_preeditRect options:NSAlignAllEdgesNearest];
// Draw the highlighted part of preedit text
@@ -2878,6 +2857,9 @@ - (void)updateLayer {
NSMaxRange(_hilitedPreeditRange) + 2 == _preeditContents.length) {
textPolygon.body.size.width += padding;
}
+ if (NSMaxX(textPolygon.body) > NSMaxX(innerBox) - 2) {
+ textPolygon.body.size.width = NSMaxX(innerBox) - NSMinX(textPolygon.body);
+ }
textPolygon.body = [self backingAlignedRect:NSIntersectionRect(textPolygon.body, innerBox)
options:NSAlignAllEdgesNearest];
}
@@ -2891,17 +2873,18 @@ - (void)updateLayer {
textPolygon.tail = [self backingAlignedRect:NSIntersectionRect(textPolygon.tail, innerBox)
options:NSAlignAllEdgesNearest];
}
- hilitedPreeditPath = [NSBezierPath squirclePathForPolygon:textPolygon cornerRadius:hilitedCornerRadius];
+ CGFloat cornerRadius = fmin(hilitedCornerRadius, fabs([_theme.preeditAttrs[NSFontAttributeName] descender]));
+ hilitedPreeditPath = [NSBezierPath squirclePathForPolygon:textPolygon cornerRadius:cornerRadius];
}
_deleteBackRect = [_preeditView blockRectForRange:NSMakeRange(_preeditContents.length - 1, 1)];
_deleteBackRect.size.width += _theme.fullWidth;
- _deleteBackRect.origin.x = NSMaxX(backgroundRect) - NSWidth(_deleteBackRect);
- _deleteBackRect.origin.y += _theme.borderInsets.height;
+ _deleteBackRect.origin.x = NSMaxX(_preeditRect) - NSWidth(_deleteBackRect);
+ _deleteBackRect.origin.y = NSMaxY(_preeditRect) - NSHeight(_deleteBackRect);
_deleteBackRect = [self backingAlignedRect:NSIntersectionRect(_deleteBackRect, _preeditRect)
options:NSAlignAllEdgesNearest];
}
- /*** Candidates Rects, all in documentView coordinates (except for `candidatesRect`) ***/
+ /* Candidates (in documentView coordinates, except for `clipRect`) */
_candidatePolygons = NULL;
_sectionRects = NULL;
_tabularIndices = NULL;
@@ -2911,7 +2894,7 @@ - (void)updateLayer {
if (!_scrollView.hidden) {
_clipRect.size.width = NSWidth(backgroundRect);
_clipRect = [self backingAlignedRect:NSIntersectionRect(_clipRect, backgroundRect)
- options:NSAlignAllEdgesNearest];
+ options:NSAlignAllEdgesNearest];
_documentRect.size.width = NSWidth(backgroundRect);
_documentRect = [_documentView backingAlignedRect:_documentRect
options:NSAlignAllEdgesNearest];
@@ -2948,6 +2931,8 @@ - (void)updateLayer {
candidatePolygon.body.size.width = NSWidth(_documentRect);
} else if (!NSIsEmptyRect(candidatePolygon.tail)) {
candidatePolygon.body.size.width += _theme.fullWidth;
+ } else if (NSMaxX(candidatePolygon.body) > NSMaxX(_documentRect) - 2) {
+ candidatePolygon.body.size.width = NSMaxX(_documentRect) - NSMinX(candidatePolygon.body);
}
candidatePolygon.body = [_documentView backingAlignedRect:NSIntersectionRect(candidatePolygon.body, _documentRect)
options:NSAlignAllEdgesNearest];
@@ -2998,7 +2983,7 @@ - (void)updateLayer {
}
}
- /*** Paging Rects ***/
+ /* Paging */
_pageUpRect = NSZeroRect;
_pageDownRect = NSZeroRect;
_expanderRect = NSZeroRect;
@@ -3011,33 +2996,30 @@ - (void)updateLayer {
_pagingRect = [self backingAlignedRect:NSIntersectionRect(_pagingRect, backgroundRect)
options:NSAlignAllEdgesNearest];
if (_theme.showPaging) {
- _pageUpRect = [_pagingView blockRectForRange:NSMakeRange(0, 1)];
- _pageDownRect = [_pagingView blockRectForRange:NSMakeRange(_pagingContents.length - 1, 1)];
- _pageDownRect.origin.x += NSMinX(_pagingRect);
+ _pageUpRect = NSOffsetRect([_pagingView blockRectForRange:NSMakeRange(0, 1)],
+ NSMinX(_pagingRect), NSMinY(_pagingRect));
+ _pageDownRect = NSOffsetRect([_pagingView blockRectForRange:NSMakeRange(_pagingContents.length - 1, 1)],
+ NSMinX(_pagingRect), NSMinY(_pagingRect));
_pageDownRect.size.width += _theme.fullWidth;
- _pageDownRect.origin.y += NSMinY(_pagingRect);
- _pageUpRect.origin.x += NSMinX(_pagingRect);
// bypass the bug of getting wrong glyph position when tab is presented
- _pageUpRect.size.width = NSWidth(_pageDownRect);
- _pageUpRect.origin.y += NSMinY(_pagingRect);
+ _pageUpRect.size = _pageDownRect.size;
_pageUpRect = [self backingAlignedRect:NSIntersectionRect(_pageUpRect, _pagingRect)
options:NSAlignAllEdgesNearest];
_pageDownRect = [self backingAlignedRect:NSIntersectionRect(_pageDownRect, _pagingRect)
options:NSAlignAllEdgesNearest];
}
if (_theme.tabular) {
- _expanderRect = [_pagingView blockRectForRange:NSMakeRange(_pagingContents.length / 2, 1)];
- _expanderRect.origin.x += NSMinX(_pagingRect);
+ _expanderRect = NSOffsetRect([_pagingView blockRectForRange:NSMakeRange(_pagingContents.length / 2, 1)],
+ NSMinX(_pagingRect), NSMinY(_pagingRect));
_expanderRect.size.width += _theme.fullWidth;
- _expanderRect.origin.y += NSMinY(_pagingRect);
_expanderRect = [self backingAlignedRect:NSIntersectionRect(_expanderRect, _pagingRect)
options:NSAlignAllEdgesNearest];
}
}
- /*** Border Rects ***/
+ /* Border */
CGFloat outerCornerRadius = fmin(_theme.cornerRadius, NSHeight(panelRect) * 0.5);
- CGFloat innerCornerRadius = clamp(_theme.hilitedCornerRadius,
+ CGFloat innerCornerRadius = clamp(hilitedCornerRadius,
outerCornerRadius - fmin(_theme.borderInsets.width, _theme.borderInsets.height),
NSHeight(backgroundRect) * 0.5);
NSBezierPath* panelPath;
@@ -3050,10 +3032,12 @@ - (void)updateLayer {
mainPanelRect.size.height -= NSHeight(_pagingRect);
NSRect tailPanelRect = NSInsetRect(NSOffsetRect(_pagingRect, 0, _theme.borderInsets.height),
-_theme.borderInsets.width, 0);
- panelPath = [NSBezierPath squirclePathForPolygon:(SquirrelTextPolygon){mainPanelRect, tailPanelRect, NSZeroRect} cornerRadius:outerCornerRadius];
+ panelPath = [NSBezierPath squirclePathForPolygon:(SquirrelTextPolygon){mainPanelRect, tailPanelRect, NSZeroRect}
+ cornerRadius:outerCornerRadius];
NSRect mainBackgroundRect = backgroundRect;
mainBackgroundRect.size.height -= NSHeight(_pagingRect);
- backgroundPath = [NSBezierPath squirclePathForPolygon:(SquirrelTextPolygon){mainBackgroundRect, _pagingRect, NSZeroRect} cornerRadius:innerCornerRadius];
+ backgroundPath = [NSBezierPath squirclePathForPolygon:(SquirrelTextPolygon){mainBackgroundRect, _pagingRect, NSZeroRect}
+ cornerRadius:innerCornerRadius];
}
NSBezierPath* borderPath = panelPath.copy;
[borderPath appendBezierPath:backgroundPath];
@@ -3063,7 +3047,7 @@ - (void)updateLayer {
[flip scaleXBy:1 yBy:-1];
NSBezierPath* shapePath = [flip transformBezierPath:panelPath];
- /*** Draw into layers ***/
+ /* Draw into layers */
if (@available(macOS 10.14, *)) {
_shape.path = shapePath.quartzPath;
}
@@ -3076,11 +3060,9 @@ - (void)updateLayer {
}
// highlighted candidate layer
if (!_scrollView.hidden) {
- NSAffineTransform* translate = NSAffineTransform.transform;
- [translate translateXBy:-NSMinX(_clipRect) yBy:-NSMinY(_clipRect)];
- _clipLayer.path = [translate transformBezierPath:clipPath].quartzPath;
+ _clipLayer.path = [NSBezierPath squirclePathForRect:_scrollView.bounds cornerRadius:hilitedCornerRadius].quartzPath;
NSBezierPath* activePagePath;
- BOOL expanded = _candidateCount > _theme.pageSize;
+ BOOL expanded = _theme.tabular && _candidateCount > _theme.pageSize;
if (expanded) {
NSRect activePageRect = _sectionRects[_hilitedCandidate / _theme.pageSize];
activePagePath = [NSBezierPath squirclePathForRect:activePageRect cornerRadius:hilitedCornerRadius];
@@ -3088,7 +3070,7 @@ - (void)updateLayer {
}
if (_theme.candidateBackColor != nil) {
NSBezierPath* nonHilitedCandidatePath = NSBezierPath.bezierPath;
- BOOL stackColors = _theme.stackColors && _theme.candidateBackColor.alphaComponent < 0.999;
+ BOOL stackColors = _theme.stackColors && _theme.candidateBackColor.alphaComponent < nexttoward(1.0, 0.0);
for (NSUInteger i = 0; i < _candidateCount; ++i) {
if (i != _hilitedCandidate) {
NSBezierPath* candidatePath = _theme.linear
@@ -3109,7 +3091,7 @@ - (void)updateLayer {
NSBezierPath* hilitedCandidatePath = _theme.linear
? [NSBezierPath squirclePathForPolygon:_candidatePolygons[_hilitedCandidate] cornerRadius:hilitedCornerRadius]
: [NSBezierPath squirclePathForRect:_candidatePolygons[_hilitedCandidate].body cornerRadius:hilitedCornerRadius];
- if (_theme.stackColors && _theme.hilitedCandidateBackColor.alphaComponent < 0.999)
+ if (_theme.stackColors && _theme.hilitedCandidateBackColor.alphaComponent < nexttoward(1.0, 0.0))
[(expanded ? activePagePath : documentPath) appendBezierPath:hilitedCandidatePath];
_hilitedCandidateLayer.path = hilitedCandidatePath.quartzPath;
_hilitedCandidateLayer.hidden = NO;
@@ -3161,7 +3143,7 @@ - (void)updateLayer {
NSBezierPath* nonCandidatePath = backgroundPath.copy;
[nonCandidatePath appendBezierPath:clipPath];
if (_theme.stackColors && _theme.hilitedPreeditBackColor != nil &&
- _theme.hilitedPreeditBackColor.alphaComponent < 0.999) {
+ _theme.hilitedPreeditBackColor.alphaComponent < nexttoward(1.0, 0.0)) {
if (hilitedPreeditPath != nil)
[nonCandidatePath appendBezierPath:hilitedPreeditPath];
if (functionButtonPath != nil)
@@ -3203,10 +3185,10 @@ - (SquirrelIndex)indexForMouseSpot:(NSPoint)spot {
@end // SquirrelView
-/* In order to put SquirrelPanel above client app windows,
- SquirrelPanel needs to be assigned a window level higher
- than kCGHelpWindowLevelKey that the system tooltips use.
- This class makes system-alike tooltips above SquirrelPanel */
+/** In order to put SquirrelPanel above client app windows,
+ SquirrelPanel needs to be assigned a window level higher
+ than `kCGHelpWindowLevelKey` that the system tooltips use.
+ This class makes system-alike tooltips above SquirrelPanel */
@interface SquirrelToolTip : NSWindow
typedef NS_CLOSED_ENUM(NSInteger, SquirrelDisplayType) {
@@ -3215,8 +3197,8 @@ typedef NS_CLOSED_ENUM(NSInteger, SquirrelDisplayType) {
@property(nonatomic, readonly, direct) BOOL empty;
-- (void)showWithToolTip:(NSString* _Nullable)toolTip
- display:(SquirrelDisplayType)display __attribute__((objc_direct));
+- (void)showToolTip:(NSString* _Nullable)toolTip
+ display:(SquirrelDisplayType)display __attribute__((objc_direct));
- (void)delayedShow:(NSTimer* _Nonnull)timer;
- (void)delayedHide:(NSTimer* _Nonnull)timer;
- (void)hide __attribute__((objc_direct));
@@ -3248,6 +3230,8 @@ - (instancetype)init {
_textView.bezeled = YES;
_textView.bezelStyle = NSTextFieldSquareBezel;
_textView.selectable = NO;
+ _textView.usesSingleLineMode = NO;
+ _textView.lineBreakMode = NSLineBreakByWordWrapping;
[contentView addSubview:_textView];
self.contentView = contentView;
_empty = YES;
@@ -3255,8 +3239,8 @@ - (instancetype)init {
return self;
}
-- (void)showWithToolTip:(NSString*)toolTip
- display:(SquirrelDisplayType)display {
+- (void)showToolTip:(NSString*)toolTip
+ display:(SquirrelDisplayType)display {
if (display == kDisplayNone || toolTip.length == 0) {
[self clear];
return;
@@ -3266,10 +3250,13 @@ - (void)showWithToolTip:(NSString*)toolTip
_empty = NO;
_textView.stringValue = toolTip;
+ _textView.preferredMaxLayoutWidth = NSWidth(panel.screen.visibleFrame) * 0.25;
_textView.font = [NSFont toolTipsFontOfSize:0];
_textView.textColor = NSColor.windowFrameTextColor;
[_textView sizeToFit];
NSSize contentSize = _textView.fittingSize;
+ contentSize.width += 3;
+ contentSize.height += 3;
NSPoint spot = NSEvent.mouseLocation;
NSCursor* cursor = NSCursor.currentSystemCursor;
@@ -3279,10 +3266,10 @@ - (void)showWithToolTip:(NSString*)toolTip
contentSize.width, contentSize.height);
NSRect screenRect = panel.screen.visibleFrame;
- if (NSMaxX(windowRect) > NSMaxX(screenRect) - 0.1) {
+ if (NSMaxX(windowRect) > nexttoward(NSMaxX(screenRect), -INFINITY)) {
windowRect.origin.x = NSMaxX(screenRect) - NSWidth(windowRect);
}
- if (NSMinY(windowRect) < NSMinY(screenRect) + 0.1) {
+ if (NSMinY(windowRect) < nexttoward(NSMinY(screenRect), INFINITY)) {
windowRect.origin.y = NSMinY(screenRect);
}
windowRect = [panel.screen backingAlignedRect:windowRect
@@ -3439,8 +3426,10 @@ - (void)getLocked __attribute__((objc_direct)) {
- (void)setIbeamRect:(NSRect)IbeamRect {
if (!NSEqualRects(_IbeamRect, IbeamRect)) {
_IbeamRect = IbeamRect;
- _needsRedraw |= YES;
- if (!NSIntersectsRect(IbeamRect, _screen.frame)) {
+ _needsRedraw = YES;
+ if (NSEqualRects(IbeamRect, NSZeroRect)) {
+ _initPosition = YES;
+ } else if (!NSIntersectsRect(_screen.frame, IbeamRect) && !NSContainsRect(_screen.frame, IbeamRect)) {
[self willChangeValueForKey:@"screen"];
[self updateScreen];
[self didChangeValueForKey:@"screen"];
@@ -3450,8 +3439,10 @@ - (void)setIbeamRect:(NSRect)IbeamRect {
}
- (void)windowDidChangeBackingProperties:(NSNotification*)notification {
- if ([notification.object isEqualTo:self])
- [self updateDisplayParameters];
+ if ([notification.object isMemberOfClass:SquirrelPanel.class]) {
+ SquirrelPanel* panel = notification.object;
+ [panel updateDisplayParameters];
+ }
}
- (void)observeValueForKeyPath:(NSString*)keyPath
@@ -3493,6 +3484,8 @@ - (instancetype)init {
self.backgroundColor = NSColor.clearColor;
self.delegate = self;
self.acceptsMouseMovedEvents = YES;
+ self.displaysWhenScreenProfileChanges = YES;
+ self.worksWhenModal = YES;
NSFlippedView* contentView = NSFlippedView.alloc.init;
contentView.autoresizesSubviews = NO;
@@ -3557,12 +3550,11 @@ - (void)updateDisplayParameters __attribute__((objc_direct)) {
[_view.theme.textAttrs[NSFontAttributeName] pointSize] / 144.0);
_textWidthLimit = ceil((_view.theme.vertical ? NSHeight(screenRect) : NSWidth(screenRect)) * textWidthRatio -
_view.theme.borderInsets.width * 2 - _view.theme.fullWidth);
- if (_view.theme.lineLength > 0.1) {
- _textWidthLimit = fmin(_view.theme.lineLength, _textWidthLimit);
+ if (isnormal(_view.theme.lineLength) && _view.theme.lineLength < _textWidthLimit) {
+ _textWidthLimit = _view.theme.lineLength;
}
if (_view.theme.tabular) {
- _textWidthLimit = floor((_textWidthLimit + _view.theme.fullWidth) / (_view.theme.fullWidth * 2)) *
- (_view.theme.fullWidth * 2) - _view.theme.fullWidth;
+ _textWidthLimit = floor(_textWidthLimit / (_view.theme.fullWidth * 2)) * (_view.theme.fullWidth * 2);
}
_view.candidateView.textContainer.size = NSMakeSize(_textWidthLimit, CGFLOAT_MAX);
_view.preeditView.textContainer.size = NSMakeSize(_textWidthLimit, CGFLOAT_MAX);
@@ -3589,7 +3581,7 @@ - (void)updateDisplayParameters __attribute__((objc_direct)) {
: NSMakeSize(widthLimit, defaultBackImage.size.height / defaultBackImage.size.width * widthLimit);
}
if (@available(macOS 10.14, *)) {
- _back.hidden = _view.theme.translucency < 0.001f;
+ _back.hidden = isfinite(_view.theme.translucency) && !isnormal(_view.theme.translucency);
if (NSImage* darkBackImage = SquirrelView.darkTheme.backImage; darkBackImage.valid) {
CGFloat widthLimit = _textWidthLimit + SquirrelView.darkTheme.fullWidth;
darkBackImage.resizingMode = NSImageResizingModeStretch;
@@ -3599,6 +3591,16 @@ - (void)updateDisplayParameters __attribute__((objc_direct)) {
}
}
[_view updateColors];
+ if (self.isVisible) {
+ [self showPreedit:[_view.preeditContents.string substringToIndex:fmax(0UL, _view.preeditContents.length - 2)]
+ selRange:_view.hilitedPreeditRange
+ caretPos:_caretPos
+ candidateIndices:_indexRange
+ hilitedCandidate:_hilitedCandidate
+ pageNum:_pageNum
+ finalPage:_finalPage
+ didCompose:YES];
+ }
}
- (NSUInteger)candidateIndexOnDirection:(SquirrelIndex)arrowKey {
@@ -3735,11 +3737,12 @@ - (void)sendEvent:(NSEvent*)event {
if (cursorIndex >= 0 && cursorIndex < _indexRange.length && _hilitedCandidate != cursorIndex) {
[self highlightFunctionButton:kVoidSymbol displayToolTip:kDisplayNone];
if (_view.theme.linear && _view.candidateInfos[cursorIndex].truncated) {
- [_toolTip showWithToolTip:[_view.candidateContents.mutableString substringWithRange:
- _view.candidateInfos[cursorIndex].candidateRange()]
- display:kDisplayNow];
+ [_toolTip showToolTip:[_view.candidateContents.mutableString substringWithRange:
+ _view.candidateInfos[cursorIndex].candidateRange()]
+ display:kDisplayNow];
} else {
- [_toolTip showWithToolTip:NSLocalizedString(@"candidate", nil) display:kDisplayOnRequest];
+ [_toolTip showToolTip:[NSBundle.mainBundle localizedStringForKey:@"candidate" value:nil table:@"Tooltips"]
+ display:kDisplayOnRequest];
}
self.sectionNum = cursorIndex / _view.theme.pageSize;
[_inputController performAction:kHIGHLIGHT
@@ -3766,7 +3769,7 @@ - (void)sendEvent:(NSEvent*)event {
scrollLocus = NSZeroPoint;
scrollByLine = NO;
} else if ((event.phase == NSEventPhaseNone || event.momentumPhase == NSEventPhaseNone) &&
- !isnan(scrollLocus.x) && !isnan(scrollLocus.y)) {
+ isfinite(scrollLocus.x) && isfinite(scrollLocus.y)) {
CGFloat scrollDistance = 0.0;
// determine scrolling direction by confining to sectors within ±30º of any axis
if (fabs(event.scrollingDeltaX) > fabs(event.scrollingDeltaY) * sqrt(3.0)) {
@@ -3779,7 +3782,7 @@ - (void)sendEvent:(NSEvent*)event {
// compare accumulated locus length against threshold and limit paging to max once
if (scrollLocus.x > scrollThreshold) {
if (_view.theme.vertical &&
- NSMaxY(_view.scrollView.documentVisibleRect) < NSMaxY(_view.documentRect) - 0.1) {
+ NSMaxY(_view.scrollView.documentVisibleRect) < nexttoward(NSMaxY(_view.documentRect), -INFINITY)) {
scrollByLine = YES;
NSPoint origin = _view.scrollView.contentView.bounds.origin;
origin.y += fmin(scrollDistance,
@@ -3792,7 +3795,7 @@ - (void)sendEvent:(NSEvent*)event {
scrollLocus = NSMakePoint(INFINITY, INFINITY);
}
} else if (scrollLocus.y > scrollThreshold) {
- if (NSMinY(_view.scrollView.documentVisibleRect) > NSMinY(_view.documentRect) + 0.1) {
+ if (NSMinY(_view.scrollView.documentVisibleRect) > nexttoward(NSMinY(_view.documentRect), INFINITY)) {
scrollByLine = YES;
NSPoint origin = _view.scrollView.contentView.bounds.origin;
origin.y -= fmin(scrollDistance,
@@ -3805,7 +3808,7 @@ - (void)sendEvent:(NSEvent*)event {
}
} else if (scrollLocus.x < -scrollThreshold) {
if (_view.theme.vertical &&
- NSMinY(_view.scrollView.documentVisibleRect) > NSMinY(_view.documentRect) + 0.1) {
+ NSMinY(_view.scrollView.documentVisibleRect) > nexttoward(NSMinY(_view.documentRect), INFINITY)) {
scrollByLine = YES;
NSPoint origin = _view.scrollView.contentView.bounds.origin;
origin.y += fmax(scrollDistance,
@@ -3818,7 +3821,7 @@ - (void)sendEvent:(NSEvent*)event {
scrollLocus = NSMakePoint(INFINITY, INFINITY);
}
} else if (scrollLocus.y < -scrollThreshold) {
- if (NSMaxY(_view.scrollView.documentVisibleRect) < NSMaxY(_view.documentRect) - 0.1) {
+ if (NSMaxY(_view.scrollView.documentVisibleRect) < nexttoward(NSMaxY(_view.documentRect), -INFINITY)) {
scrollByLine = YES;
NSPoint origin = _view.scrollView.contentView.bounds.origin;
origin.y -= fmax(scrollDistance,
@@ -3860,15 +3863,15 @@ - (void)highlightCandidate:(NSUInteger)hilitedCandidate __attribute__((objc_dire
NSColor* labelColor = priorCandidate == priorHilitedCandidate && _sectionNum == priorSectionNum ?
_view.theme.labelForeColor : _view.theme.dimmedLabelForeColor;
[_view.candidateContents addAttribute:NSForegroundColorAttributeName
- value:labelColor
- range:priorRange.labelRange()];
+ value:labelColor
+ range:priorRange.labelRange()];
if (priorCandidate == priorHilitedCandidate) {
[_view.candidateContents addAttribute:NSForegroundColorAttributeName
- value:_view.theme.textForeColor
- range:priorRange.textRange()];
+ value:_view.theme.textForeColor
+ range:priorRange.textRange()];
[_view.candidateContents addAttribute:NSForegroundColorAttributeName
- value:_view.theme.commentForeColor
- range:priorRange.commentRange()];
+ value:_view.theme.commentForeColor
+ range:priorRange.commentRange()];
}
}
NSUInteger newCandidate = i + _sectionNum * _view.theme.pageSize;
@@ -3878,15 +3881,15 @@ - (void)highlightCandidate:(NSUInteger)hilitedCandidate __attribute__((objc_dire
NSColor* labelColor = newCandidate == hilitedCandidate ?
_view.theme.hilitedLabelForeColor : _view.theme.labelForeColor;
[_view.candidateContents addAttribute:NSForegroundColorAttributeName
- value:labelColor
- range:newRange.labelRange()];
+ value:labelColor
+ range:newRange.labelRange()];
if (newCandidate == hilitedCandidate) {
[_view.candidateContents addAttribute:NSForegroundColorAttributeName
- value:_view.theme.hilitedTextForeColor
- range:newRange.textRange()];
+ value:_view.theme.hilitedTextForeColor
+ range:newRange.textRange()];
[_view.candidateContents addAttribute:NSForegroundColorAttributeName
- value:_view.theme.hilitedCommentForeColor
- range:newRange.commentRange()];
+ value:_view.theme.hilitedCommentForeColor
+ range:newRange.commentRange()];
}
}
}
@@ -3926,30 +3929,36 @@ - (void)highlightFunctionButton:(SquirrelIndex)functionButton
value:_view.theme.hilitedPreeditForeColor
range:NSMakeRange(0, 1)];
functionButton = _pageNum == 0 ? kHomeKey : kPageUpKey;
- [_toolTip showWithToolTip:NSLocalizedString(_pageNum == 0 ? @"home" : @"page_up", nil) display:display];
+ [_toolTip showToolTip:[NSBundle.mainBundle
+ localizedStringForKey:_pageNum == 0 ? @"home" : @"page_up"
+ value:nil table:@"Tooltips"] display:display];
break;
case kPageDownKey:
[_view.pagingContents addAttribute:NSForegroundColorAttributeName
value:_view.theme.hilitedPreeditForeColor
range:NSMakeRange(_view.pagingContents.length - 1, 1)];
functionButton = _finalPage ? kEndKey : kPageDownKey;
- [_toolTip showWithToolTip:NSLocalizedString(_finalPage ? @"end" : @"page_down", nil) display:display];
+ [_toolTip showToolTip:[NSBundle.mainBundle
+ localizedStringForKey:_finalPage ? @"end" : @"page_down"
+ value:nil table:@"Tooltips"] display:display];
break;
case kExpandButton:
[_view.pagingContents addAttribute:NSForegroundColorAttributeName
value:_view.theme.hilitedPreeditForeColor
range:NSMakeRange(_view.pagingContents.length / 2, 1)];
functionButton = _locked ? kLockButton : _view.expanded ? kCompressButton : kExpandButton;
- [_toolTip showWithToolTip:NSLocalizedString(_locked ? @"unlock" : _view.expanded ?
- @"compress" : @"expand", nil) display:display];
+ [_toolTip showToolTip:[NSBundle.mainBundle
+ localizedStringForKey:_locked ? @"unlock" : _view.expanded ? @"compress" : @"expand"
+ value:nil table:@"Tooltips"] display:display];
break;
case kBackSpaceKey:
[_view.preeditContents addAttribute:NSForegroundColorAttributeName
value:_view.theme.hilitedPreeditForeColor
range:NSMakeRange(_view.preeditContents.length - 1, 1)];
functionButton = _caretPos == NSNotFound || _caretPos == 0 ? kEscapeKey : kBackSpaceKey;
- [_toolTip showWithToolTip:NSLocalizedString(_caretPos == NSNotFound || _caretPos == 0 ?
- @"escape" : @"delete", nil) display:display];
+ [_toolTip showToolTip:[NSBundle.mainBundle
+ localizedStringForKey:_caretPos == NSNotFound || _caretPos == 0 ? @"escape" : @"delete"
+ value:nil table:@"Tooltips"] display:display];
break;
}
[_view highlightFunctionButton:functionButton];
@@ -3983,31 +3992,34 @@ - (void)show __attribute__((objc_direct)) {
BOOL sweepVertical = NSWidth(_IbeamRect) > NSHeight(_IbeamRect);
NSRect contentRect = _view.contentRect;
// fixed line length (text width), but not applicable to status message
- if (theme.lineLength > 0.1 && _statusMessage == nil) {
+ if (isnormal(theme.lineLength) && _statusMessage == nil) {
contentRect.size.width = _textWidthLimit;
}
- /* remember panel size (fix the top leading anchor of the panel in screen coordiantes)
- but only when the text would expand on the side of upstream (i.e. towards the beginning of text) */
+ // remember panel size (fix the top leading anchor of the panel in screen coordiantes)
+ // but only when the text would expand on the side of upstream (i.e. towards the beginning of text)
if (theme.rememberSize && _view.statusView.hidden) {
- if (theme.lineLength < 0.1 && theme.vertical
+ if (isfinite(theme.lineLength) && !isnormal(theme.lineLength)) {
+ BOOL attained = theme.vertical
? sweepVertical ? (NSMinY(_IbeamRect) - fmax(NSWidth(contentRect), _maxSizeAttained.width)
- - border.width - floor(theme.fullWidth * 0.5) < NSMinY(screenRect) + 0.1)
+ - border.width - floor(theme.fullWidth * 0.5) < nexttoward(NSMinY(screenRect), INFINITY))
: (NSMinY(_IbeamRect) - kOffsetGap - NSHeight(screenRect) * textWidthRatio
- - border.width * 2 - theme.fullWidth < NSMinY(screenRect) + 0.1)
+ - border.width * 2 - theme.fullWidth < nexttoward(NSMinY(screenRect), INFINITY))
: sweepVertical ? (NSMinX(_IbeamRect) - kOffsetGap - NSWidth(screenRect) * textWidthRatio
- - border.width * 2 - theme.fullWidth > NSMinX(screenRect) + 0.1)
+ - border.width * 2 - theme.fullWidth > nexttoward(NSMinX(screenRect), INFINITY))
: (NSMaxX(_IbeamRect) + fmax(NSWidth(contentRect), _maxSizeAttained.width)
- + border.width + floor(theme.fullWidth * 0.5) > NSMaxX(screenRect) - 0.1)) {
- if (NSWidth(contentRect) > _maxSizeAttained.width + 0.1) {
- _maxSizeAttained.width = NSWidth(contentRect);
- } else {
- contentRect.size.width = _maxSizeAttained.width;
+ + border.width + floor(theme.fullWidth * 0.5) > nexttoward(NSMaxX(screenRect), -INFINITY));
+ if (attained) {
+ if (NSWidth(contentRect) > nexttoward(_maxSizeAttained.width, INFINITY)) {
+ _maxSizeAttained.width = NSWidth(contentRect);
+ } else {
+ contentRect.size.width = _maxSizeAttained.width;
+ }
}
}
CGFloat textHeight = fmax(NSHeight(contentRect), _maxSizeAttained.height) + border.height * 2;
- if (theme.vertical ? (NSMinX(_IbeamRect) - textHeight - (sweepVertical ? kOffsetGap : 0) < NSMinX(screenRect) + 0.1)
- : (NSMinY(_IbeamRect) - textHeight - (sweepVertical ? 0 : kOffsetGap) < NSMinY(screenRect) + 0.1)) {
- if (NSHeight(contentRect) > _maxSizeAttained.height + 0.1) {
+ if (theme.vertical ? (NSMinX(_IbeamRect) - textHeight - (sweepVertical ? kOffsetGap : 0) < nexttoward(NSMinX(screenRect), INFINITY))
+ : (NSMinY(_IbeamRect) - textHeight - (sweepVertical ? 0 : kOffsetGap) < nexttoward(NSMinY(screenRect), INFINITY))) {
+ if (NSHeight(contentRect) > nexttoward(_maxSizeAttained.height, INFINITY)) {
_maxSizeAttained.height = NSHeight(contentRect);
} else {
contentRect.size.height = _maxSizeAttained.height;
@@ -4019,22 +4031,15 @@ but only when the text would expand on the side of upstream (i.e. towards the be
if (_statusMessage != nil) {
// following system UI, middle-align status message with cursor
_initPosition = YES;
- if (theme.vertical) {
- windowRect.size.width = NSHeight(contentRect) + border.height * 2;
- windowRect.size.height = NSWidth(contentRect) + border.width * 2 + theme.fullWidth;
- } else {
- windowRect.size.width = NSWidth(contentRect) + border.width * 2 + theme.fullWidth;
- windowRect.size.height = NSHeight(contentRect) + border.height * 2;
- }
- if (sweepVertical) {
- // vertically centre-align (MidY) in screen coordinates
- windowRect.origin.x = NSMinX(_IbeamRect) - kOffsetGap - NSWidth(windowRect);
- windowRect.origin.y = NSMidY(_IbeamRect) - NSHeight(windowRect) * 0.5;
- } else {
- // horizontally centre-align (MidX) in screen coordinates
- windowRect.origin.x = NSMidX(_IbeamRect) - NSWidth(windowRect) * 0.5;
- windowRect.origin.y = NSMinY(_IbeamRect) - kOffsetGap - NSHeight(windowRect);
- }
+ windowRect.size = theme.vertical ? NSMakeSize(NSHeight(contentRect) + border.height * 2,
+ NSWidth(contentRect) + border.width * 2 + theme.fullWidth)
+ : NSMakeSize(NSWidth(contentRect) + border.width * 2 + theme.fullWidth,
+ NSHeight(contentRect) + border.height * 2);
+ // vertically/horizontally centre-align (midY/midX) in screen coordinates
+ windowRect.origin = sweepVertical ? NSMakePoint(NSMinX(_IbeamRect) - kOffsetGap - NSWidth(windowRect),
+ NSMidY(_IbeamRect) - NSHeight(windowRect) * 0.5)
+ : NSMakePoint(NSMidX(_IbeamRect) - NSWidth(windowRect) * 0.5,
+ NSMinY(_IbeamRect) - kOffsetGap - NSHeight(windowRect));
} else {
if (theme.vertical) {
// anchor is the top right corner in screen coordinates (MaxX, MaxY)
@@ -4042,24 +4047,21 @@ but only when the text would expand on the side of upstream (i.e. towards the be
NSMaxY(self.frame) - NSWidth(contentRect) - border.width * 2 - theme.fullWidth,
NSHeight(contentRect) + border.height * 2,
NSWidth(contentRect) + border.width * 2 + theme.fullWidth);
- _initPosition |= NSIntersectsRect(windowRect, _IbeamRect) || !NSContainsRect(screenRect, windowRect);
+ _initPosition |= NSIntersectsRect(windowRect, _IbeamRect) || NSContainsRect(windowRect, _IbeamRect) ||
+ (!NSContainsRect(screenRect, windowRect) && !NSIntersectsRect(screenRect, windowRect));
if (_initPosition) {
if (!sweepVertical) {
// To avoid jumping up and down while typing, use the lower screen when typing on upper, and vice versa
- if (NSMinY(_IbeamRect) - kOffsetGap - NSHeight(screenRect) * textWidthRatio -
- border.width * 2 - theme.fullWidth < NSMinY(screenRect) + 0.1) {
- windowRect.origin.y = NSMaxY(_IbeamRect) + kOffsetGap;
- } else {
- windowRect.origin.y = NSMinY(_IbeamRect) - kOffsetGap - NSHeight(windowRect);
- }
+ BOOL isOnLower = NSMinY(_IbeamRect) - kOffsetGap - NSHeight(screenRect) * textWidthRatio -
+ border.width * 2 - theme.fullWidth < nexttoward(NSMinY(screenRect), INFINITY);
+ windowRect.origin.y = isOnLower ? NSMaxY(_IbeamRect) + kOffsetGap
+ : NSMinY(_IbeamRect) - kOffsetGap - NSHeight(windowRect);
// Make the right edge of candidate block fixed at the left of cursor
windowRect.origin.x = NSMinX(_IbeamRect) + border.height - NSWidth(windowRect);
} else {
- if (NSMinX(_IbeamRect) - kOffsetGap - NSWidth(windowRect) < NSMinX(screenRect) + 0.1) {
- windowRect.origin.x = NSMaxX(_IbeamRect) + kOffsetGap;
- } else {
- windowRect.origin.x = NSMinX(_IbeamRect) - kOffsetGap - NSWidth(windowRect);
- }
+ BOOL isOnLefter = NSMinX(_IbeamRect) - kOffsetGap - NSWidth(windowRect) < nexttoward(NSMinX(screenRect), INFINITY);
+ windowRect.origin.x = isOnLefter ? NSMaxX(_IbeamRect) + kOffsetGap
+ : NSMinX(_IbeamRect) - kOffsetGap - NSWidth(windowRect);
windowRect.origin.y = NSMinY(_IbeamRect) + border.width + ceil(theme.fullWidth * 0.5) - NSHeight(windowRect);
}
}
@@ -4069,22 +4071,20 @@ but only when the text would expand on the side of upstream (i.e. towards the be
NSMaxY(self.frame) - NSHeight(contentRect) - border.height * 2,
NSWidth(contentRect) + border.width * 2 + theme.fullWidth,
NSHeight(contentRect) + border.height * 2);
- _initPosition |= NSIntersectsRect(windowRect, _IbeamRect) || !NSContainsRect(screenRect, windowRect);
+ _initPosition |= NSIntersectsRect(windowRect, _IbeamRect) || NSContainsRect(windowRect, _IbeamRect) ||
+ (!NSContainsRect(screenRect, windowRect) && !NSIntersectsRect(screenRect, windowRect));
if (_initPosition) {
if (sweepVertical) {
// To avoid jumping left and right while typing, use the lefter screen when typing on righter, and vice versa
- if (NSMinX(_IbeamRect) - kOffsetGap - NSWidth(screenRect) * textWidthRatio - border.width * 2 - theme.fullWidth > NSMinX(screenRect) + 0.1) {
- windowRect.origin.x = NSMinX(_IbeamRect) - kOffsetGap - NSWidth(windowRect);
- } else {
- windowRect.origin.x = NSMaxX(_IbeamRect) + kOffsetGap;
- }
+ BOOL isOnLefter = NSMinX(_IbeamRect) - kOffsetGap - NSWidth(screenRect) * textWidthRatio -
+ border.width * 2 - theme.fullWidth > nexttoward(NSMinX(screenRect), INFINITY);
+ windowRect.origin.x = isOnLefter ? NSMinX(_IbeamRect) - kOffsetGap - NSWidth(windowRect)
+ : NSMaxX(_IbeamRect) + kOffsetGap;
windowRect.origin.y = NSMinY(_IbeamRect) + border.height - NSHeight(windowRect);
} else {
- if (NSMinY(_IbeamRect) - kOffsetGap - NSHeight(windowRect) < NSMinY(screenRect) + 0.1) {
- windowRect.origin.y = NSMaxY(_IbeamRect) + kOffsetGap;
- } else {
- windowRect.origin.y = NSMinY(_IbeamRect) - kOffsetGap - NSHeight(windowRect);
- }
+ BOOL isOnLower = NSMinY(_IbeamRect) - kOffsetGap - NSHeight(windowRect) < nexttoward(NSMinY(screenRect), INFINITY);
+ windowRect.origin.y = isOnLower ? NSMaxY(_IbeamRect) + kOffsetGap
+ : NSMinY(_IbeamRect) - kOffsetGap - NSHeight(windowRect);
windowRect.origin.x = NSMaxX(_IbeamRect) - border.width - ceil(theme.fullWidth * 0.5);
}
}
@@ -4106,19 +4106,19 @@ but only when the text would expand on the side of upstream (i.e. towards the be
}
}
- if (NSMaxX(windowRect) > NSMaxX(screenRect) - 0.1) {
+ if (NSMaxX(windowRect) > nexttoward(NSMaxX(screenRect), -INFINITY)) {
windowRect.origin.x = (_initPosition && sweepVertical ? fmin(NSMinX(_IbeamRect) - kOffsetGap, NSMaxX(screenRect)) :
NSMaxX(screenRect)) - NSWidth(windowRect);
}
- if (NSMinX(windowRect) < NSMinX(screenRect) + 0.1) {
+ if (NSMinX(windowRect) < nexttoward(NSMinX(screenRect), INFINITY)) {
windowRect.origin.x = _initPosition && sweepVertical ?
fmax(NSMaxX(_IbeamRect) + kOffsetGap, NSMinX(screenRect)) : NSMinX(screenRect);
}
- if (NSMinY(windowRect) < NSMinY(screenRect) + 0.1) {
+ if (NSMinY(windowRect) < nexttoward(NSMinY(screenRect), INFINITY)) {
windowRect.origin.y = _initPosition && !sweepVertical ?
fmax(NSMaxY(_IbeamRect) + kOffsetGap, NSMinY(screenRect)) : NSMinY(screenRect);
}
- if (NSMaxY(windowRect) > NSMaxY(screenRect) - 0.1) {
+ if (NSMaxY(windowRect) > nexttoward(NSMaxY(screenRect), -INFINITY)) {
windowRect.origin.y = (_initPosition && !sweepVertical ? fmin(NSMinY(_IbeamRect) - kOffsetGap, NSMaxY(screenRect)) :
NSMaxY(screenRect)) - NSHeight(windowRect);
}
@@ -4138,27 +4138,23 @@ but only when the text would expand on the side of upstream (i.e. towards the be
NSRect viewRect = NSIntegralRectWithOptions(self.contentView.bounds, NSAlignAllEdgesNearest);
_view.frame = viewRect;
if (!_view.statusView.hidden) {
- _view.statusView.frame = NSMakeRect(NSMinX(viewRect) + border.width + ceil(theme.fullWidth * 0.5) -
- _view.statusView.textContainerOrigin.x,
+ _view.statusView.frame = NSMakeRect(NSMinX(viewRect) + border.width + ceil(theme.fullWidth * 0.5) - _view.statusView.textContainerOrigin.x,
NSMinY(viewRect) + border.height - _view.statusView.textContainerOrigin.y,
NSWidth(viewRect) - border.width * 2 - theme.fullWidth,
NSHeight(viewRect) - border.height * 2);
}
if (!_view.preeditView.hidden) {
- _view.preeditView.frame = NSMakeRect(NSMinX(viewRect) + border.width + ceil(theme.fullWidth * 0.5) -
- _view.preeditView.textContainerOrigin.x,
+ _view.preeditView.frame = NSMakeRect(NSMinX(viewRect) + border.width + ceil(theme.fullWidth * 0.5) - _view.preeditView.textContainerOrigin.x,
NSMinY(viewRect) + border.height - _view.preeditView.textContainerOrigin.y,
NSWidth(viewRect) - border.width * 2 - theme.fullWidth,
NSHeight(_view.preeditRect));
}
if (!_view.pagingView.hidden) {
CGFloat leadOrigin = theme.linear ? NSMaxX(viewRect) - NSWidth(_view.pagingRect) - border.width + ceil(theme.fullWidth * 0.5)
- : NSMinX(viewRect) + border.width + ceil(theme.fullWidth * 0.5);
+ : NSMinX(viewRect) + border.width + ceil(theme.fullWidth * 0.5);
_view.pagingView.frame = NSMakeRect(leadOrigin - _view.pagingView.textContainerOrigin.x,
- NSMaxY(viewRect) - border.height - NSHeight(_view.pagingRect) -
- _view.pagingView.textContainerOrigin.y,
- (theme.linear ? NSWidth(_view.pagingRect)
- : NSWidth(viewRect) - border.width * 2) - theme.fullWidth,
+ NSMaxY(viewRect) - border.height - NSHeight(_view.pagingRect) - _view.pagingView.textContainerOrigin.y,
+ (theme.linear ? NSWidth(_view.pagingRect) : NSWidth(viewRect) - border.width * 2) - theme.fullWidth,
NSHeight(_view.pagingRect));
}
if (!_view.scrollView.hidden) {
@@ -4168,9 +4164,9 @@ but only when the text would expand on the side of upstream (i.e. towards the be
NSHeight(_view.clipRect));
_view.documentView.frame = NSMakeRect(0.0, 0.0, NSWidth(viewRect) - border.width * 2, NSHeight(_view.documentRect));
_view.candidateView.frame = NSMakeRect(ceil(theme.fullWidth * 0.5) - _view.candidateView.textContainerOrigin.x,
- ceil(theme.lineSpacing * 0.5) - _view.candidateView.textContainerOrigin.y,
- NSWidth(viewRect) - border.width * 2 - theme.fullWidth,
- NSHeight(_view.documentRect) - theme.lineSpacing);
+ floor(theme.lineSpacing * 0.5) - _view.candidateView.textContainerOrigin.y,
+ NSWidth(viewRect) - border.width * 2 - theme.fullWidth,
+ NSHeight(_view.documentRect) - theme.lineSpacing);
}
if (!_back.hidden) {
_back.frame = viewRect;
@@ -4190,22 +4186,11 @@ - (void)hide {
[_toolTip hide];
[self orderOut:nil];
_maxSizeAttained = NSZeroSize;
- _initPosition = YES;
+ _IbeamRect = NSZeroRect;
self.expanded = NO;
self.sectionNum = 0;
}
-static CGFloat textWidth(NSAttributedString* string, BOOL vertical) {
- if (vertical) {
- NSMutableAttributedString* verticalString = string.mutableCopy;
- [verticalString addAttribute:NSVerticalGlyphFormAttributeName
- value:@YES range:NSMakeRange(0, verticalString.length)];
- return ceil(verticalString.size.width);
- } else {
- return ceil(string.size.width);
- }
-}
-
// Main function to add attributes to text output from librime
- (void)showPreedit:(NSString*)preedit
selRange:(NSRange)selRange
@@ -4252,7 +4237,7 @@ - (void)showPreedit:(NSString*)preedit
SquirrelCandidateInfo* candidateInfos;
if (updateCandidates) {
[_view.candidateContents deleteCharactersInRange:NSMakeRange(0, _view.candidateContents.length)];
- if (theme.lineLength > 0.1) {
+ if (isnormal(theme.lineLength)) {
_maxSizeAttained.width = fmin(theme.lineLength, _textWidthLimit);
}
_indexRange = indexRange;
@@ -4276,7 +4261,7 @@ - (void)showPreedit:(NSString*)preedit
value:padding
range:NSMakeRange(selRange.location - 1, 1)];
}
- if (NSMaxRange(selRange) < _view.preeditContents.length) {
+ if (NSMaxRange(selRange) < _view.preeditContents.length - 1) {
[_view.preeditContents addAttribute:NSKernAttributeName
value:padding
range:NSMakeRange(NSMaxRange(selRange) - 1, 1)];
@@ -4324,13 +4309,13 @@ - (void)showPreedit:(NSString*)preedit
? theme.candidateHilitedTemplate.mutableCopy : theme.candidateTemplate.mutableCopy;
// plug in enumerator, candidate text and comment into the template
NSRange enumRange = [candidate.mutableString rangeOfString:@"%c"];
- [candidate replaceCharactersInRange:enumRange withString:theme.labels[col]];
+ [candidate replaceCharactersInRange:enumRange withString:theme.rawLabels[col]];
NSRange textRange = [candidate.mutableString rangeOfString:@"%@"];
NSString* text = _inputController.candidateTexts[idx + indexRange.location];
[candidate replaceCharactersInRange:textRange withString:text];
- NSRange commentRange = [candidate.mutableString rangeOfString:kTipSpecifier];
+ NSRange commentRange = [candidate.mutableString rangeOfString:@"%s"];
NSString* comment = _inputController.candidateComments[idx + indexRange.location];
if (comment.length > 0) {
[candidate replaceCharactersInRange:commentRange withString:[@"\u00A0" append:comment]];
@@ -4355,9 +4340,9 @@ - (void)showPreedit:(NSString*)preedit
for (NSUInteger i = 1; i <= idx; ++i) {
if (i == idx || candidateInfos[i].truncated != truncated) {
[_view.candidateContents addAttribute:NSParagraphStyleAttributeName
- value:truncated ? theme.truncatedParagraphStyle
- : theme.candidateParagraphStyle
- range:NSMakeRange(location, candidateInfos[i - 1].maxRange() - location)];
+ value:truncated ? theme.truncatedParagraphStyle
+ : theme.candidateParagraphStyle
+ range:NSMakeRange(location, candidateInfos[i - 1].maxRange() - location)];
if (i < idx) {
truncated = candidateInfos[i].truncated;
location = candidateInfos[i].location;
@@ -4366,8 +4351,8 @@ - (void)showPreedit:(NSString*)preedit
}
} else {
[_view.candidateContents addAttribute:NSParagraphStyleAttributeName
- value:theme.candidateParagraphStyle
- range:NSMakeRange(0, _view.candidateContents.length)];
+ value:theme.candidateParagraphStyle
+ range:NSMakeRange(0, _view.candidateContents.length)];
}
}
}
@@ -4381,7 +4366,7 @@ - (void)showPreedit:(NSString*)preedit
SquirrelCandidateInfo info = {.location = candidateStart, .text = textRange.location, .comment = NSMaxRange(textRange), .idx = idx, .col = col};
[_view.candidateContents appendAttributedString:candidate];
// for linear layout, middle-truncate candidates that are longer than one line
- if (theme.linear && textWidth(candidate, theme.vertical) >
+ if (theme.linear && NSWidth([_view.candidateView blockRectForRange:NSMakeRange(candidateStart, candidate.length)]) >
_textWidthLimit - theme.fullWidth * (theme.tabular ? 3 : 2)) {
info.length = _view.candidateContents.length - candidateStart;
info.truncated = YES;
@@ -4390,8 +4375,8 @@ - (void)showPreedit:(NSString*)preedit
[_view.candidateContents.mutableString appendString:@"\n"];
}
[_view.candidateContents addAttribute:NSParagraphStyleAttributeName
- value:theme.truncatedParagraphStyle
- range:NSMakeRange(candidateStart, _view.candidateContents.length - candidateStart)];
+ value:theme.truncatedParagraphStyle
+ range:NSMakeRange(candidateStart, _view.candidateContents.length - candidateStart)];
} else {
if (theme.linear || idx < indexRange.length - 1) {
// separator: linear = "\u3000\x1D"; tabular = "\u3000\t\x1D"; stacked = "\n"
@@ -4436,14 +4421,16 @@ - (void)showPreedit:(NSString*)preedit
paging:indexRange.length > 0 && (theme.tabular || theme.showPaging)];
CGFloat textWidth = clamp(NSWidth(_view.contentRect), _maxSizeAttained.width, _textWidthLimit);
// right-align the backward delete symbol
- if (preedit.length > 0 && rulerAttrsPreedit == nil) {
- [_view.preeditContents replaceCharactersInRange:NSMakeRange(_view.preeditContents.length - 2, 1)
- withString:@"\t"];
+ if (preedit.length > 0 &&
+ (rulerAttrsPreedit == nil || rulerAttrsPreedit.tabStops[0].location < nexttoward(textWidth, 0.0))) {
+ if (rulerAttrsPreedit == nil) {
+ [_view.preeditContents replaceCharactersInRange:NSMakeRange(_view.preeditContents.length - 2, 1)
+ withString:@"\t"];
+ }
NSMutableParagraphStyle* rulerAttrs = theme.preeditParagraphStyle.mutableCopy;
- rulerAttrs.tabStops = @[[NSTextTab.alloc
- initWithTextAlignment:NSTextAlignmentRight
- location:textWidth
- options:@{}]];
+ rulerAttrs.tabStops = @[[NSTextTab.alloc initWithTextAlignment:NSTextAlignmentRight
+ location:textWidth
+ options:@{}]];
[_view.preeditContents addAttribute:NSParagraphStyleAttributeName
value:rulerAttrs
range:NSMakeRange(0, _view.preeditContents.length)];
@@ -4508,7 +4495,6 @@ - (void)showStatus:(NSString*)message __attribute__((objc_direct)) {
paging:NO];
// disable remember_size and fixed line_length for status messages
- _initPosition = YES;
_maxSizeAttained = NSZeroSize;
if (_statusTimer.valid) {
[_statusTimer invalidate];
@@ -4565,11 +4551,10 @@ - (void)loadConfig:(SquirrelConfig*)config {
}
- (void)updateScriptVariant {
- [SquirrelView.defaultTheme setScriptVariant:_optionSwitcher.currentScriptVariant];
+ [SquirrelView.defaultTheme updateScriptVariant:_optionSwitcher.currentScriptVariant];
if (@available(macOS 10.14, *)) {
- [SquirrelView.darkTheme setScriptVariant:_optionSwitcher.currentScriptVariant];
+ [SquirrelView.darkTheme updateScriptVariant:_optionSwitcher.currentScriptVariant];
}
}
@end // SquirrelPanel
-
diff --git a/Localizable.xcstrings b/Tooltips.xcstrings
similarity index 62%
rename from Localizable.xcstrings
rename to Tooltips.xcstrings
index c3cf5fc66..c1b8e1010 100644
--- a/Localizable.xcstrings
+++ b/Tooltips.xcstrings
@@ -2,6 +2,7 @@
"sourceLanguage" : "en",
"strings" : {
"candidate" : {
+ "extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -30,7 +31,7 @@
}
},
"compress" : {
- "extractionState" : "stale",
+ "extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -59,7 +60,7 @@
}
},
"delete" : {
- "extractionState" : "stale",
+ "extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -87,124 +88,8 @@
}
}
},
- "deploy_failure" : {
- "extractionState" : "stale",
- "localizations" : {
- "en" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Error occurred. See log file $TMPDIR/rime.squirrel.INFO."
- }
- },
- "zh-Hans" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "有错误!请查看日志 $TMPDIR/rime.squirrel.INFO"
- }
- },
- "zh-Hant" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "有錯誤!請查看日誌 $TMPDIR/rime.squirrel.INFO"
- }
- },
- "zh-HK" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "有錯誤!請查看日誌 $TMPDIR/rime.squirrel.INFO"
- }
- }
- }
- },
- "deploy_start" : {
- "extractionState" : "stale",
- "localizations" : {
- "en" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Deploying Rime input method engine…"
- }
- },
- "zh-Hans" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "部署输入法引擎…"
- }
- },
- "zh-Hant" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "部署輸入法引擎⋯"
- }
- },
- "zh-HK" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "部署輸入法引擎⋯"
- }
- }
- }
- },
- "deploy_success" : {
- "extractionState" : "stale",
- "localizations" : {
- "en" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Squirrel is ready."
- }
- },
- "zh-Hans" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "部署完成。"
- }
- },
- "zh-Hant" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "部署完成。"
- }
- },
- "zh-HK" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "部署完成。"
- }
- }
- }
- },
- "deploy_update" : {
- "extractionState" : "stale",
- "localizations" : {
- "en" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Deploying Rime for updates…"
- }
- },
- "zh-Hans" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "更新输入法引擎…"
- }
- },
- "zh-Hant" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "更新輸入法引擎⋯"
- }
- },
- "zh-HK" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "更新輸入法引擎⋯"
- }
- }
- }
- },
"end" : {
- "extractionState" : "stale",
+ "extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -233,7 +118,7 @@
}
},
"escape" : {
- "extractionState" : "stale",
+ "extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -262,7 +147,7 @@
}
},
"expand" : {
- "extractionState" : "stale",
+ "extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -291,7 +176,7 @@
}
},
"home" : {
- "extractionState" : "stale",
+ "extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -320,7 +205,7 @@
}
},
"page_down" : {
- "extractionState" : "stale",
+ "extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -349,7 +234,7 @@
}
},
"page_up" : {
- "extractionState" : "stale",
+ "extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -377,86 +262,8 @@
}
}
},
- "problematic_launch" : {
- "localizations" : {
- "en" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Problematic launch detected!\nSquirrel may be suffering a crash due to improper configurations.\nRevert previous modifications to see if the problem recurs."
- }
- },
- "zh-Hans" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "检测到启动有问题!\n“鼠须管”可能因错误设置而崩溃。\n请尝试撤销之前的修改,然后查看问题是否仍旧存在。"
- }
- },
- "zh-Hant" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "啟動時偵測到問題!\n「鼠鬚管」可能因設定不當而崩潰。\n請嘗試回退先前的修改,然後查看問題是否依然存在。"
- }
- },
- "zh-HK" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "啟動時偵測到錯誤!\n「鼠鬚筆」可能由於設定不當而崩潰。\n請嘗試回退先前的改動,然後查看問題是否仍然存在。"
- }
- }
- }
- },
- "say_voice" : {
- "localizations" : {
- "en" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Alex"
- }
- },
- "zh-Hans" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "TingTing"
- }
- },
- "zh-Hant" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "MeiJia"
- }
- },
- "zh-HK" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Sinji"
- }
- }
- }
- },
- "Squirrel" : {
- "localizations" : {
- "zh-Hans" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "鼠须管"
- }
- },
- "zh-Hant" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "鼠鬚管"
- }
- },
- "zh-HK" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "鼠鬚筆"
- }
- }
- }
- },
"unlock" : {
- "extractionState" : "stale",
+ "extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
diff --git a/input_source.mm b/input_source.mm
index 9cefff264..e4e71b383 100644
--- a/input_source.mm
+++ b/input_source.mm
@@ -14,7 +14,7 @@ typedef CF_OPTIONS(CFIndex, RimeInputMode) {
CANT_INPUT_MODE = 1 << 2
};
-RimeInputMode GetEnabledInputModes(void);
+RimeInputMode GetEnabledInputModes(Boolean includeAllInstalled);
CFArrayRef GetPreferredLocale(void) {
CFTypeRef locales[] = {CFSTR("zh-Hans"), CFSTR("zh-Hant"), CFSTR("zh-HK")};
@@ -27,76 +27,61 @@ CFArrayRef GetPreferredLocale(void) {
CFArrayRef GetInputSourceList(Boolean includeAllInstalled) {
CFTypeRef keys[] = {kTISPropertyBundleID};
CFTypeRef values[] = {CFBundleGetIdentifier(CFBundleGetMainBundle())};
- CFDictionaryRef property = CFDictionaryCreate(NULL, keys, values, 1,
- &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ CFDictionaryRef property = CFDictionaryCreate(NULL, keys, values, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFArrayRef sourceList = TISCreateInputSourceList(property, includeAllInstalled);
CFRelease(property);
return sourceList;
}
void RegisterInputSource(void) {
- if (GetEnabledInputModes() != 0) { // Already registered
+ if (GetEnabledInputModes(true) != 0) { // Already registered
NSLog(@"Squirrel is already registered.");
return;
}
CFStringRef installPath = CFSTR("/Library/Input Methods/Squirrel.app");
- if (CFURLRef installURL = CFURLCreateWithFileSystemPath
- (NULL, installPath, kCFURLPOSIXPathStyle, false)) {
- OSStatus registerError = TISRegisterInputSource((CFURLRef)CFAutorelease(installURL));
- if (registerError == noErr) {
- NSLog(@"Squirrel has been successfully registered at %@", installPath);
+ if (CFURLRef installURL = CFURLCreateWithFileSystemPath(NULL, installPath, kCFURLPOSIXPathStyle, false)) {
+ if (OSStatus error = TISRegisterInputSource((CFURLRef)CFAutorelease(installURL)) != noErr) {
+ NSLog(@"Squirrel failed to register at %@ (error code: %d)", installPath, error);
} else {
- NSLog(@"Squirrel failed to register at %@ (%@)", installPath,
- [NSError errorWithDomain:NSOSStatusErrorDomain
- code:registerError userInfo:nil]);
+ NSLog(@"Squirrel has been successfully registered at %@", installPath);
}
}
}
-void EnableInputSource(void) {
- if (GetEnabledInputModes() != 0) {
+void EnableInputSource(RimeInputMode modesToEnable) {
+ if (GetEnabledInputModes(false) != 0) {
// keep user's manually enabled input modes
NSLog(@"Squirrel input method(s) is already enabled.");
return;
}
- RimeInputMode input_modes_to_enable = 0;
- CFArrayRef preferred = GetPreferredLocale();
- if (CFArrayGetCount(preferred) > 0) {
- CFStringRef language = (CFStringRef)CFArrayGetValueAtIndex(preferred, 0);
- if (CFStringCompare(language, CFSTR("zh-Hans"),
- kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
- input_modes_to_enable |= HANS_INPUT_MODE;
- } else if (CFStringCompare(language, CFSTR("zh-Hant"),
- kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
- input_modes_to_enable |= HANT_INPUT_MODE;
- } else if (CFStringCompare(language, CFSTR("zh-HK"),
- kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
- input_modes_to_enable |= CANT_INPUT_MODE;
+ if (modesToEnable == 0) {
+ CFArrayRef preferred = GetPreferredLocale();
+ if (CFArrayGetCount(preferred) > 0) {
+ CFStringRef language = (CFStringRef)CFArrayGetValueAtIndex(preferred, 0);
+ if (CFStringCompare(language, CFSTR("zh-Hans"), kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
+ modesToEnable |= HANS_INPUT_MODE;
+ } else if (CFStringCompare(language, CFSTR("zh-Hant"), kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
+ modesToEnable |= HANT_INPUT_MODE;
+ } else if (CFStringCompare(language, CFSTR("zh-HK"), kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
+ modesToEnable |= CANT_INPUT_MODE;
+ }
+ } else {
+ modesToEnable = HANS_INPUT_MODE;
}
- } else {
- input_modes_to_enable = HANS_INPUT_MODE;
+ CFRelease(preferred);
}
- CFRelease(preferred);
CFArrayRef sourceList = GetInputSourceList(true);
for (CFIndex i = 0; i < CFArrayGetCount(sourceList); ++i) {
- TISInputSourceRef inputSource = (TISInputSourceRef)
- CFArrayGetValueAtIndex(sourceList, i);
- CFStringRef sourceID = (CFStringRef)TISGetInputSourceProperty
- (inputSource, kTISPropertyInputSourceID);
+ TISInputSourceRef inputSource = (TISInputSourceRef)CFArrayGetValueAtIndex(sourceList, i);
+ CFStringRef sourceID = (CFStringRef)TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceID);
// NSLog(@"Examining input source: %@", sourceID);
- if ((CFStringCompare(sourceID, kHansInputModeID, 0) == kCFCompareEqualTo &&
- (input_modes_to_enable & HANS_INPUT_MODE)) ||
- (CFStringCompare(sourceID, kHantInputModeID, 0) == kCFCompareEqualTo &&
- (input_modes_to_enable & HANT_INPUT_MODE)) ||
- (CFStringCompare(sourceID, kCantInputModeID, 0) == kCFCompareEqualTo &&
- (input_modes_to_enable & CANT_INPUT_MODE))) {
- CFBooleanRef isEnabled = (CFBooleanRef)TISGetInputSourceProperty
- (inputSource, kTISPropertyInputSourceIsEnabled);
+ if ((CFStringCompare(sourceID, kHansInputModeID, 0) == kCFCompareEqualTo && (modesToEnable & HANS_INPUT_MODE)) ||
+ (CFStringCompare(sourceID, kHantInputModeID, 0) == kCFCompareEqualTo && (modesToEnable & HANT_INPUT_MODE)) ||
+ (CFStringCompare(sourceID, kCantInputModeID, 0) == kCFCompareEqualTo && (modesToEnable & CANT_INPUT_MODE))) {
+ CFBooleanRef isEnabled = (CFBooleanRef)TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceIsEnabled);
if (!CFBooleanGetValue(isEnabled)) {
- if (OSStatus enableError = TISEnableInputSource(inputSource) != noErr) {
- NSLog(@"Failed to enable input source: %@ (%@)", sourceID,
- [NSError errorWithDomain:NSOSStatusErrorDomain
- code:enableError userInfo:nil]);
+ if (OSStatus error = TISEnableInputSource(inputSource) != noErr) {
+ NSLog(@"Failed to enable input source: %@ (error code: %d)", sourceID, error);
} else {
NSLog(@"Enabled input source: %@", sourceID);
}
@@ -106,54 +91,46 @@ void EnableInputSource(void) {
CFRelease(sourceList);
}
-void SelectInputSource(void) {
- RimeInputMode enabled_input_modes = GetEnabledInputModes();
- RimeInputMode input_mode_to_select = 0;
- CFArrayRef preferred = GetPreferredLocale();
- for (CFIndex i = 0; i < CFArrayGetCount(preferred); ++i) {
- CFStringRef language = (CFStringRef)CFArrayGetValueAtIndex(preferred, i);
- if (CFStringCompare(language, CFSTR("zh-Hans"), kCFCompareCaseInsensitive)
- == kCFCompareEqualTo && (enabled_input_modes & HANS_INPUT_MODE)) {
- input_mode_to_select = HANS_INPUT_MODE;
- break;
- } else if (CFStringCompare(language, CFSTR("zh-Hant"), kCFCompareCaseInsensitive)
- == kCFCompareEqualTo && (enabled_input_modes & HANT_INPUT_MODE)) {
- input_mode_to_select = HANT_INPUT_MODE;
- break;
- } else if (CFStringCompare(language, CFSTR("zh-HK"), kCFCompareCaseInsensitive)
- == kCFCompareEqualTo && (enabled_input_modes & CANT_INPUT_MODE)) {
- input_mode_to_select = CANT_INPUT_MODE;
- break;
+void SelectInputSource(RimeInputMode modeToSelect) {
+ RimeInputMode enabledModes = GetEnabledInputModes(false);
+ if (modeToSelect == 0 || (enabledModes & modeToSelect) == 0) {
+ CFArrayRef preferred = GetPreferredLocale();
+ for (CFIndex i = 0; i < CFArrayGetCount(preferred); ++i) {
+ CFStringRef language = (CFStringRef)CFArrayGetValueAtIndex(preferred, i);
+ if (CFStringCompare(language, CFSTR("zh-Hans"), kCFCompareCaseInsensitive) == kCFCompareEqualTo &&
+ (enabledModes & HANS_INPUT_MODE)) {
+ modeToSelect = HANS_INPUT_MODE;
+ break;
+ } else if (CFStringCompare(language, CFSTR("zh-Hant"), kCFCompareCaseInsensitive) == kCFCompareEqualTo &&
+ (enabledModes & HANT_INPUT_MODE)) {
+ modeToSelect = HANT_INPUT_MODE;
+ break;
+ } else if (CFStringCompare(language, CFSTR("zh-HK"), kCFCompareCaseInsensitive) == kCFCompareEqualTo &&
+ (enabledModes & CANT_INPUT_MODE)) {
+ modeToSelect = CANT_INPUT_MODE;
+ break;
+ }
}
+ CFRelease(preferred);
}
- CFRelease(preferred);
- if (input_mode_to_select == 0) {
+ if (modeToSelect == 0) {
NSLog(@"No enabled input sources.");
return;
}
CFArrayRef sourceList = GetInputSourceList(false);
for (CFIndex i = 0; i < CFArrayGetCount(sourceList); ++i) {
- TISInputSourceRef inputSource = (TISInputSourceRef)
- CFArrayGetValueAtIndex(sourceList, i);
- CFStringRef sourceID = (CFStringRef)TISGetInputSourceProperty(
- inputSource, kTISPropertyInputSourceID);
+ TISInputSourceRef inputSource = (TISInputSourceRef)CFArrayGetValueAtIndex(sourceList, i);
+ CFStringRef sourceID = (CFStringRef)TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceID);
// NSLog(@"Examining input source: %@", sourceID);
- if ((CFStringCompare(sourceID, kHansInputModeID, 0) == kCFCompareEqualTo &&
- ((input_mode_to_select & HANS_INPUT_MODE) != 0)) ||
- (CFStringCompare(sourceID, kHantInputModeID, 0) == kCFCompareEqualTo &&
- ((input_mode_to_select & HANT_INPUT_MODE) != 0)) ||
- (CFStringCompare(sourceID, kCantInputModeID, 0) == kCFCompareEqualTo &&
- ((input_mode_to_select & CANT_INPUT_MODE) != 0))) {
+ if ((CFStringCompare(sourceID, kHansInputModeID, 0) == kCFCompareEqualTo && (modeToSelect & HANS_INPUT_MODE)) ||
+ (CFStringCompare(sourceID, kHantInputModeID, 0) == kCFCompareEqualTo && (modeToSelect & HANT_INPUT_MODE)) ||
+ (CFStringCompare(sourceID, kCantInputModeID, 0) == kCFCompareEqualTo && (modeToSelect & CANT_INPUT_MODE))) {
// select the first enabled input mode in Squirrel.
- CFBooleanRef isSelectable = (CFBooleanRef)TISGetInputSourceProperty(
- inputSource, kTISPropertyInputSourceIsSelectCapable);
- CFBooleanRef isSelected = (CFBooleanRef)TISGetInputSourceProperty(
- inputSource, kTISPropertyInputSourceIsSelected);
+ CFBooleanRef isSelectable = (CFBooleanRef)TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceIsSelectCapable);
+ CFBooleanRef isSelected = (CFBooleanRef)TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceIsSelected);
if (!CFBooleanGetValue(isSelected) && CFBooleanGetValue(isSelectable)) {
- if (OSStatus selectError = TISSelectInputSource(inputSource) != 0) {
- NSLog(@"Failed to select input source: %@ (%@)", sourceID,
- [NSError errorWithDomain:NSOSStatusErrorDomain
- code:selectError userInfo:nil]);
+ if (OSStatus error = TISSelectInputSource(inputSource) != noErr) {
+ NSLog(@"Failed to select input source: %@ (error code: %d)", sourceID, error);
} else {
NSLog(@"Selected input source: %@", sourceID);
break;
@@ -167,18 +144,14 @@ void SelectInputSource(void) {
void DisableInputSource(void) {
CFArrayRef sourceList = GetInputSourceList(false);
for (CFIndex i = CFArrayGetCount(sourceList); i > 0; --i) {
- TISInputSourceRef inputSource = (TISInputSourceRef)
- CFArrayGetValueAtIndex(sourceList, i - 1);
- CFStringRef sourceID = (CFStringRef)TISGetInputSourceProperty
- (inputSource, kTISPropertyInputSourceID);
+ TISInputSourceRef inputSource = (TISInputSourceRef)CFArrayGetValueAtIndex(sourceList, i - 1);
+ CFStringRef sourceID = (CFStringRef)TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceID);
// NSLog(@"Examining input source: %@", sourceID);
if (CFStringCompare(sourceID, kHansInputModeID, 0) == kCFCompareEqualTo ||
CFStringCompare(sourceID, kHantInputModeID, 0) == kCFCompareEqualTo ||
CFStringCompare(sourceID, kCantInputModeID, 0) == kCFCompareEqualTo) {
- if (OSStatus disableError = TISDisableInputSource(inputSource) != 0) {
- NSLog(@"Failed to disable input source: %@ (%@)", sourceID,
- [NSError errorWithDomain:NSOSStatusErrorDomain
- code:disableError userInfo:nil]);
+ if (OSStatus error = TISDisableInputSource(inputSource) != noErr) {
+ NSLog(@"Failed to disable input source: %@ (error code: %d)", sourceID, error);
} else {
NSLog(@"Disabled input source: %@", sourceID);
}
@@ -187,14 +160,12 @@ void DisableInputSource(void) {
CFRelease(sourceList);
}
-RimeInputMode GetEnabledInputModes(void) {
+RimeInputMode GetEnabledInputModes(Boolean includeAllInstalled) {
RimeInputMode input_modes = 0;
- CFArrayRef sourceList = GetInputSourceList(false);
+ CFArrayRef sourceList = GetInputSourceList(includeAllInstalled);
for (CFIndex i = 0; i < CFArrayGetCount(sourceList); ++i) {
- TISInputSourceRef inputSource = (TISInputSourceRef)
- CFArrayGetValueAtIndex(sourceList, i);
- CFStringRef sourceID = (CFStringRef)TISGetInputSourceProperty
- (inputSource, kTISPropertyInputSourceID);
+ TISInputSourceRef inputSource = (TISInputSourceRef)CFArrayGetValueAtIndex(sourceList, i);
+ CFStringRef sourceID = (CFStringRef)TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceID);
// NSLog(@"Examining input source: %@", sourceID);
if (CFStringCompare(sourceID, kHansInputModeID, 0) == kCFCompareEqualTo) {
input_modes |= HANS_INPUT_MODE;
diff --git a/main.mm b/main.mm
index ee36e306d..ce34a4b1d 100644
--- a/main.mm
+++ b/main.mm
@@ -5,14 +5,17 @@
#import
#import
+typedef CF_OPTIONS(CFIndex, RimeInputMode) {
+ DEFAULT_INPUT_MODE = 1 << 0,
+ HANS_INPUT_MODE = 1 << 0,
+ HANT_INPUT_MODE = 1 << 1,
+ CANT_INPUT_MODE = 1 << 2
+};
+
void RegisterInputSource(void);
void DisableInputSource(void);
-void EnableInputSource(void);
-void SelectInputSource(void);
-
-// Each input method needs a unique connection name.
-// Note that periods and spaces are not allowed in the connection name.
-static NSString* const kConnectionName = @"Squirrel_1_Connection";
+void EnableInputSource(RimeInputMode modesToEnable);
+void SelectInputSource(RimeInputMode modeToSelect);
int main(int argc, char* argv[]) {
if (argc > 1 && strcmp("--quit", argv[1]) == 0) {
@@ -39,7 +42,19 @@ int main(int argc, char* argv[]) {
}
if (argc > 1 && strcmp("--enable-input-source", argv[1]) == 0) {
- EnableInputSource();
+ RimeInputMode modesToEnable = 0;
+ if (argc > 2) {
+ for (int i = 2; i < argc; ++i) {
+ if (strcmp("Hans", argv[i]) == 0 || strcmp("hans", argv[i]) == 0 || strcmp("HANS", argv[i])) {
+ modesToEnable |= HANS_INPUT_MODE;
+ } else if (strcmp("Hant", argv[i]) == 0 || strcmp("hant", argv[i]) == 0 || strcmp("HANT", argv[i])) {
+ modesToEnable |= HANT_INPUT_MODE;
+ } else if (strcmp("Cant", argv[i]) == 0 || strcmp("cant", argv[i]) == 0 || strcmp("CANT", argv[i])) {
+ modesToEnable |= CANT_INPUT_MODE;
+ }
+ }
+ }
+ EnableInputSource(modesToEnable);
return 0;
}
@@ -49,7 +64,19 @@ int main(int argc, char* argv[]) {
}
if (argc > 1 && strcmp("--select-input-source", argv[1]) == 0) {
- SelectInputSource();
+ RimeInputMode modeToSelect = 0;
+ if (argc > 2) {
+ for (int i = 2; i < argc; ++i) {
+ if (strcmp("Hans", argv[i]) == 0 || strcmp("hans", argv[i]) == 0 || strcmp("HANS", argv[i])) {
+ modeToSelect |= HANS_INPUT_MODE;
+ } else if (strcmp("Hant", argv[i]) == 0 || strcmp("hant", argv[i]) == 0 || strcmp("HANT", argv[i])) {
+ modeToSelect |= HANT_INPUT_MODE;
+ } else if (strcmp("Cant", argv[i]) == 0 || strcmp("cant", argv[i]) == 0 || strcmp("CANT", argv[i])) {
+ modeToSelect |= CANT_INPUT_MODE;
+ }
+ }
+ }
+ SelectInputSource(modeToSelect);
return 0;
}
@@ -89,8 +116,8 @@ int main(int argc, char* argv[]) {
if (NSApp.squirrelAppDelegate.problematicLaunchDetected) {
NSLog(@"Problematic launch detected!");
- NSArray* args = @[@"-v", NSLocalizedString(@"say_voice", nil),
- NSLocalizedString(@"problematic_launch", nil)];
+ NSArray* args = @[@"-v", [NSBundle.mainBundle localizedStringForKey:@"say_voice" value:nil table:@"Notifications"],
+ [NSBundle.mainBundle localizedStringForKey:@"problematic_launch" value:nil table:@"Notifications"]];
if (@available(macOS 10.13, *)) {
NSURL* say = [NSURL fileURLWithPath:@"/usr/bin/say" isDirectory:NO];
[NSTask launchedTaskWithExecutableURL:say
diff --git a/mul.lproj/MainMenu.xcstrings b/mul.lproj/MainMenu.xcstrings
index a695a6408..19c83388a 100644
--- a/mul.lproj/MainMenu.xcstrings
+++ b/mul.lproj/MainMenu.xcstrings
@@ -2,8 +2,7 @@
"sourceLanguage" : "en",
"strings" : {
"774.title" : {
- "comment" : "Class = \"NSMenuItem\"; title = \"Deploy\"; ObjectID = \"774\";",
- "extractionState" : "extracted_with_value",
+ "extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -32,8 +31,7 @@
}
},
"776.title" : {
- "comment" : "Class = \"NSMenuItem\"; title = \"Check for updates…\"; ObjectID = \"776\";",
- "extractionState" : "extracted_with_value",
+ "extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -62,8 +60,7 @@
}
},
"780.title" : {
- "comment" : "Class = \"NSMenuItem\"; title = \"ㄓ⃣Squirrel Switcher\"; ObjectID = \"780\";",
- "extractionState" : "extracted_with_value",
+ "extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -92,8 +89,7 @@
}
},
"797.title" : {
- "comment" : "Class = \"NSMenuItem\"; title = \"Rime Wiki…\"; ObjectID = \"797\";",
- "extractionState" : "extracted_with_value",
+ "extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -122,8 +118,7 @@
}
},
"802.title" : {
- "comment" : "Class = \"NSMenuItem\"; title = \"Settings…\"; ObjectID = \"802\";",
- "extractionState" : "extracted_with_value",
+ "extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -152,8 +147,7 @@
}
},
"804.title" : {
- "comment" : "Class = \"NSMenuItem\"; title = \"Sync user data\"; ObjectID = \"804\";",
- "extractionState" : "extracted_with_value",
+ "extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -182,8 +176,7 @@
}
},
"809.title" : {
- "comment" : "Class = \"NSMenuItem\"; title = \"Error and warning logs\"; ObjectID = \"809\";",
- "extractionState" : "extracted_with_value",
+ "extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -213,4 +206,4 @@
}
},
"version" : "1.0"
-}
\ No newline at end of file
+}
diff --git a/package/sign_update b/package/sign_update
new file mode 100755
index 000000000..06779e730
Binary files /dev/null and b/package/sign_update differ