diff --git a/docs/design_doc/mac_version.md b/docs/design_doc/mac_version.md index 25ff5e812..7e9b2a008 100644 --- a/docs/design_doc/mac_version.md +++ b/docs/design_doc/mac_version.md @@ -23,13 +23,10 @@ convert the NSEvent to Mozc's key event and send to the converter. Also, this class handles the OS status change such like mode change by mouse clicks. IMKInputServer handles all of the connection between applications, and invokes -IMKInputController. Normally we don't need to inherit IMKInputServer but use the -standard server class directly, but we do inherit this as -GoogleJapaneseInputServer for some reasons. See *Communication with renderer* -section for the details. +IMKInputController. The IMKit.framework configurations are in Info.plist of the client application. -You'll see the GoogleJapaneseInputController class name in mac/Info.plist. +You'll see the MozcImkInputController class name in mac/Info.plist. ## Communication with converter @@ -95,10 +92,8 @@ the client. That's tricky because renderer is an IPC server and client does not run Mozc's IPC server system -- it already has a standard NSRunloop event handling due to IMKit.framework. -This is why we inherit IMKInputServer, as I described above. The -GoogleJapaneseInputServer registers a new event handling for a new connection -actually. When a click happens, the renderer packs the protobuf data into a -binary string, pack it into NSData, and send this to the client's connection by +When a click happens, the renderer packs the protobuf data into a +binary string, pack it into NSData, and sends this to the client's connection by a standard Cocoa's IPC using NSConnection. You may notice "wait, does NSConnection cause memory leaks?" This is absolutely diff --git a/src/MODULE.bazel b/src/MODULE.bazel index 9327f442b..576944c1d 100644 --- a/src/MODULE.bazel +++ b/src/MODULE.bazel @@ -267,8 +267,8 @@ http_archive( # Since the URL is shared among multiple versions, we cannot specify the SHA256. # For offline build (with the --repository_cache flag), SHA256 should be specified. # -# SHA256 as of 2024-08-14 -# SHA256_ZIP_CODE_KEN_ALL = "d2d177ef64b9459a618d8aaa96b6c5f081cb3f37f6e9c00ba912001e66b81f3e" +# SHA256 as of 2024-10-08 +# SHA256_ZIP_CODE_KEN_ALL = "e5a32c0199ebc890ea5c48c06fd817a168b26d68ce1c3a86ba0ae1cdf6c2d1c4" SHA256_ZIP_CODE_KEN_ALL = None http_archive( @@ -278,8 +278,8 @@ http_archive( url = "https://www.post.japanpost.jp/zipcode/dl/kogaki/zip/ken_all.zip", ) -# SHA256 as of 2024-08-14 -# SHA256_ZIP_CODE_JIGYOSYO="ab6fd92df35e63c4566d29f70456da1603d8178ce3fec073f7c27b28d0af2e10" +# SHA256 as of 2024-10-08 +# SHA256_ZIP_CODE_JIGYOSYO = "5d52511ce6613fcc0c5b24ac59b463e927cff76ac466c2ae07a511a64bb5913f" SHA256_ZIP_CODE_JIGYOSYO = None http_archive( diff --git a/src/mac/BUILD.bazel b/src/mac/BUILD.bazel index ca613674c..7966893a8 100644 --- a/src/mac/BUILD.bazel +++ b/src/mac/BUILD.bazel @@ -106,8 +106,8 @@ mozc_objc_library( ], tags = ["manual"], deps = [ - ":imk_controller", - ":imk_server", + ":mozc_imk_input_controller", + ":renderer_receiver", "//base:const", "//base:crash_report_handler", "//base:init_mozc", @@ -120,13 +120,9 @@ mozc_objc_library( ) mozc_objc_library( - name = "imk_controller", - srcs = [ - "GoogleJapaneseInputController.mm", - ], - hdrs = [ - "GoogleJapaneseInputController.h", - ], + name = "mozc_imk_input_controller", + srcs = ["mozc_imk_input_controller.mm"], + hdrs = ["mozc_imk_input_controller.h"], data = [ "English.lproj/Config.xib", "Japanese.lproj/Config.xib", @@ -138,13 +134,10 @@ mozc_objc_library( "InputMethodKit", ], tags = ["manual"], - textual_hdrs = [ - "GoogleJapaneseInputControllerInterface.h", - ], deps = [ ":common", - ":imk_server", ":keycode_map", + ":renderer_receiver", "//base:const", "//base:process", "//base:util", @@ -164,12 +157,12 @@ mozc_objc_library( ) mozc_objc_test( - name = "imk_controller_test", + name = "mozc_imk_input_controller_test", size = "small", - srcs = ["GoogleJapaneseInputController_test.mm"], + srcs = ["mozc_imk_input_controller_test.mm"], deps = [ - ":imk_controller", ":keycode_map", + ":mozc_imk_input_controller", "//base/mac:mac_util", "//client:client_mock", "//protocol:candidates_cc_proto", @@ -182,30 +175,22 @@ mozc_objc_test( ) mozc_objc_library( - name = "imk_server", - srcs = ["GoogleJapaneseInputServer.mm"], - hdrs = ["GoogleJapaneseInputServer.h"], - sdk_frameworks = [ - "Foundation", - "InputMethodKit", - ], - tags = ["manual"], + name = "renderer_receiver", + srcs = ["renderer_receiver.mm"], + hdrs = ["renderer_receiver.h"], + sdk_frameworks = ["Foundation"], deps = [ ":common", - "//base:const", "//protocol:commands_cc_proto", - "@com_google_absl//absl/base", - "@com_google_absl//absl/log", ], ) mozc_objc_test( - name = "imk_server_test", + name = "renderer_receiver_test", size = "small", - srcs = ["GoogleJapaneseInputServer_test.mm"], - sdk_frameworks = ["InputMethodKit"], + srcs = ["renderer_receiver_test.mm"], deps = [ - ":imk_server", + ":renderer_receiver", "//protocol:commands_cc_proto", "//testing:gunit_main_objc", ], diff --git a/src/mac/GoogleJapaneseInputController.h b/src/mac/GoogleJapaneseInputController.h deleted file mode 100644 index 82edddf5b..000000000 --- a/src/mac/GoogleJapaneseInputController.h +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright 2010-2021, Google Inc. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#import -#import - -#import "mac/KeyCodeMap.h" -#import "mac/common.h" - -#include -#include -#include - -// For mozc::commands::CompositionMode -#include "client/client_interface.h" -#include "protocol/commands.pb.h" -#include "protocol/config.pb.h" -#include "protocol/renderer_command.pb.h" -#include "renderer/renderer_interface.h" - -/** GoogleJapaneseInputController is a subclass of |IMKInputController|, which holds a connection - * from a client application to the mozc server (Japanese IME server) on the machine. - * - * For the detail of IMKInputController itself, see the ADC document - * http://developer.apple.com/documentation/Cocoa/Reference/IMKInputController_Class/ - */ -@interface GoogleJapaneseInputController : IMKInputController { - @private - /** Instance variables are all strong references. */ - - /** |composedString_| stores the current preedit text */ - NSMutableAttributedString *composedString_; - - /** |originalString_| stores original key strokes. */ - NSMutableString *originalString_; - - /** |cursorPosition_| is the position of cursor in the preedit. If no cursor is found, its value - * should be -1. */ - int cursorPosition_; - - /** |mode_| stores the current input mode (Direct or conversion). */ - mozc::commands::CompositionMode mode_; - - /** |yenSignCharacter_| holds the character for the YEN_SIGN key in JIS keyboard. This config is - * separated from |keyCodeMap_| because it is for DIRECT mode. */ - mozc::config::Config::YenSignCharacter yenSignCharacter_; - - /** |suppressSuggestion_| indicates whether to suppress the suggestion. */ - bool suppressSuggestion_; - - /** |keyCodeMap_| manages the mapping between Mac key code and mozc key events. */ - KeyCodeMap *keyCodeMap_; - - /** |clientBundle_| is the Bundle ID of the client application which the controller communicates - * with. */ - std::string clientBundle_; - - NSRange replacementRange_; - - /** |lastKeyDownTime_| and |lastKeyCode_| are used to handle double tapping. */ - NSTimeInterval lastKeyDownTime_; - uint16_t lastKeyCode_; - - /** |mozcRenderer_| controls the candidate windows. */ - std::unique_ptr mozcRenderer_; - - /** |rendererCommand_| stores the command sent to |mozcRenderer_| */ - mozc::commands::RendererCommand rendererCommand_; - - /** |mozcClient_| manages connection to the mozc server. */ - std::unique_ptr mozcClient_; - - /** |imkServer_| holds the reference to GoogleJapaneseInputServer. */ - id imkServer_; - - /** |imkClientForTest_| holds the reference to the client object for unit test. */ - id imkClientForTest_; - - /** |menu_| is the NSMenu to be shown in the pulldown menu-list of the IME. */ - IBOutlet NSMenu *menu_; -} - -/** sendCommand: is called to send SessionCommand to the server from the renderer, when the user - * clicks a candidate item in candidate windows or when the renderer sends the usage stats event - * information. */ -- (void)sendCommand:(const mozc::commands::SessionCommand &)command; - -/** reconversionClicked: is called when the user clicks "Reconversion" menu item. */ -- (IBAction)reconversionClicked:(id)sender; - -/** configClicked: is called when the user clicks "Configure Mozc..." menu item. */ -- (IBAction)configClicked:(id)sender; - -/** dictionaryToolClicked: is called when the user clicks "Dictionary Tool..." menu item. */ -- (IBAction)dictionaryToolClicked:(id)sender; - -/** registerWordClicked: is called when the user clicks "Add a word..." menu item. */ -- (IBAction)registerWordClicked:(id)sender; - -/** aboutDialogClicked: is called when the user clicks "About Mozc..." menu item. */ -- (IBAction)aboutDialogClicked:(id)sender; - -/** outputResult: put result text in the specified |output| into the client application. */ -- (void)outputResult:(mozc::commands::Output *)output; - -/** Sets the ClientInterface to use in the controller. */ -- (void)setMozcClient:(std::unique_ptr)newMozcClient; - -/** Sets the RendererInterface to use in the controller. */ -- (void)setRenderer:(std::unique_ptr)newRenderer; -@end diff --git a/src/mac/GoogleJapaneseInputControllerInterface.h b/src/mac/GoogleJapaneseInputControllerInterface.h deleted file mode 100644 index db85fff84..000000000 --- a/src/mac/GoogleJapaneseInputControllerInterface.h +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright 2010-2021, Google Inc. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#import -#import - -#import "mac/KeyCodeMap.h" - -#include "client/client_interface.h" -#include "protocol/commands.pb.h" -#include "renderer/renderer_interface.h" - -@interface GoogleJapaneseInputController () -/** Updates |composedString_| from the result of a key event and put the updated composed string to - * the client application. */ -- (void)updateComposedString:(const mozc::commands::Preedit *)preedit; - -/** Updates |candidates_| from the result of a key event. */ -- (void)updateCandidates:(const mozc::commands::Output *)output; - -/** Clears all candidate data in |candidates_|. */ -- (void)clearCandidates; - -/** Opens a link specified by the URL. */ -- (void)openLink:(NSURL *)url; - -/** Auxiliary methods for switchMode: below. */ -- (void)switchModeToDirect:(id)sender; -- (void)switchModeInternal:(mozc::commands::CompositionMode)new_mode; - -/** Switches to a new mode and sync the current mode with the converter. */ -- (void)switchMode:(mozc::commands::CompositionMode)new_mode client:(id)sender; - -/** Switches the mode icon in the task bar according to |mode_|. */ -- (void)switchDisplayMode; - -/** Commits the specified text to the current client. */ -- (void)commitText:(const char *)text client:(id)sender; - -/** Conducts the reconvert event. It could have several tricks such like invoking UNDO instead if - * nothing is selected. |sender| has to be the proxy object to the client application, which might - * not be same as the sender of the click event itself when the user clicks the menu item. */ -- (void)invokeReconvert:(const mozc::commands::SessionCommand *)command client:(id)sender; - -/** Conducts the undo command. */ -- (void)invokeUndo:(id)sender; - -/** Processes output fields such as preedit, output text, candidates, and modes and calls methods - * above. */ -- (void)processOutput:(const mozc::commands::Output *)output client:(id)sender; - -/** Obtains the current configuration from the server and update client-specific configurations. */ -- (void)handleConfig; - -/** Sets up the client capability */ -- (void)setupCapability; -/** Sets up the client bundle for the sender. */ -- (void)setupClientBundle:(id)sender; - -/** Launches the word register tool with the current selection range. */ -- (void)launchWordRegisterTool:(id)client; - -/** Fills the surrounding context (preceding_text and following_text). Returns false if fails to get - * the surrounding context from the client. */ -- (BOOL)fillSurroundingContext:(mozc::commands::Context *)context client:(id)client; - -/** These are externally accessible to achieve tests. */ -@property(readonly) mozc::client::ClientInterface *mozcClient; -@property(readwrite, retain, nonatomic) KeyCodeMap *keyCodeMap; -@property(readonly) mozc::renderer::RendererInterface *renderer; -@property(readonly) mozc::config::Config::YenSignCharacter yenSignCharacter; -@property(readwrite, assign) mozc::commands::CompositionMode mode; -@property(readonly) const mozc::commands::RendererCommand &rendererCommand; -@property(readwrite, assign) NSRange replacementRange; -@property(readwrite, retain) id imkClientForTest; -@end diff --git a/src/mac/Info.plist b/src/mac/Info.plist index 6cf80e840..e5cf33ccb 100644 --- a/src/mac/Info.plist +++ b/src/mac/Info.plist @@ -180,11 +180,11 @@ InputMethodConnectionName ${PRODUCT_NAME}_1_Connection InputMethodServerControllerClass - GoogleJapaneseInputController + MozcImkInputController InputMethodServerDelegateClass - GoogleJapaneseInputController + MozcImkInputController InputMethodServerDataSourceClass - GoogleJapaneseInputController + MozcImkInputController LSBackgroundOnly 1 LSUIElement diff --git a/src/mac/README.md b/src/mac/README.md index f894009d2..bf22c0363 100644 --- a/src/mac/README.md +++ b/src/mac/README.md @@ -6,8 +6,7 @@ It contains * Installer * Uninstaller -* Interface classes for macOS IMF (i.e. subclasses of `IMKController` and - `IMKServer`). +* Interface classes for macOS IMF (i.e. the subclass of `IMKInputController`). ## ActivatePane / DevConfirmPane diff --git a/src/mac/mac.gyp b/src/mac/mac.gyp index 9dfa2fd69..4091ee9c7 100644 --- a/src/mac/mac.gyp +++ b/src/mac/mac.gyp @@ -57,12 +57,12 @@ 'conditions': [ ['OS=="mac"', { 'sources': [ - 'GoogleJapaneseInputController.mm', - 'GoogleJapaneseInputController_test.mm', - 'GoogleJapaneseInputServer.mm', - 'GoogleJapaneseInputServer_test.mm', 'KeyCodeMap.mm', 'KeyCodeMap_test.mm', + 'mozc_imk_input_controller.mm', + 'mozc_imk_input_controller_test.mm', + 'renderer_receiver.mm', + 'renderer_receiver_test.mm', ], 'link_settings': { 'libraries': [ @@ -267,10 +267,10 @@ 'type': 'executable', 'mac_bundle': 1, 'sources': [ - 'GoogleJapaneseInputController.mm', - 'GoogleJapaneseInputServer.mm', 'KeyCodeMap.mm', 'main.mm', + 'mozc_imk_input_controller.mm', + 'renderer_receiver.mm', ], 'product_name': '<(branding)', 'dependencies': [ diff --git a/src/mac/main.mm b/src/mac/main.mm index b9c1f2acd..ad7cafb49 100644 --- a/src/mac/main.mm +++ b/src/mac/main.mm @@ -28,11 +28,13 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import +#import +#import -#include +#import "mac/mozc_imk_input_controller.h" +#import "mac/renderer_receiver.h" -#import "mac/GoogleJapaneseInputController.h" -#import "mac/GoogleJapaneseInputServer.h" +#include #include "absl/flags/flag.h" #include "absl/log/log.h" @@ -59,21 +61,18 @@ int main(int argc, char *argv[]) { NSBundle *bundle = [NSBundle mainBundle]; NSDictionary *infoDictionary = [bundle infoDictionary]; NSString *connectionName = [infoDictionary objectForKey:@"InputMethodConnectionName"]; - GoogleJapaneseInputServer *imkServer = - [[GoogleJapaneseInputServer alloc] initWithName:connectionName - bundleIdentifier:[bundle bundleIdentifier]]; - + IMKServer *imkServer = [[IMKServer alloc] initWithName:connectionName + bundleIdentifier:[bundle bundleIdentifier]]; if (!imkServer) { LOG(FATAL) << mozc::kProductNameInEnglish << " failed to initialize"; return -1; } DLOG(INFO) << mozc::kProductNameInEnglish << " initialized"; - // This is a workaroud due to the crash issue on macOS 15. - NSOperatingSystemVersion versionInfo = [[NSProcessInfo processInfo] operatingSystemVersion]; - if (versionInfo.majorVersion < 15) { - [imkServer registerRendererConnection]; - } + NSString *rendererConnectionName = @kProductPrefix "_Renderer_Connection"; + RendererReceiver *rendererReceiver = + [[RendererReceiver alloc] initWithName:rendererConnectionName]; + [MozcImkInputController setGlobalRendererReceiver:rendererReceiver]; // Start the converter server at this time explicitly to prevent the // slow-down of the response for initial key event. diff --git a/src/mac/mozc_imk_input_controller.h b/src/mac/mozc_imk_input_controller.h new file mode 100644 index 000000000..825ca28e2 --- /dev/null +++ b/src/mac/mozc_imk_input_controller.h @@ -0,0 +1,277 @@ +// Copyright 2010-2021, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#import +#import + +#import "mac/KeyCodeMap.h" +#import "mac/common.h" +#import "mac/renderer_receiver.h" + +#include +#include +#include + +// For mozc::commands::CompositionMode +#include "client/client_interface.h" +#include "protocol/commands.pb.h" +#include "protocol/config.pb.h" +#include "protocol/renderer_command.pb.h" +#include "renderer/renderer_interface.h" + +/** MozcImkInputController is a subclass of |IMKInputController|, which holds a connection + * from a client application to the mozc server (Japanese IME server) on the machine. + * + * For the detail of IMKInputController itself, see the ADC document + * http://developer.apple.com/documentation/Cocoa/Reference/IMKInputController_Class/ + */ +@interface MozcImkInputController : IMKInputController { + @private + /** Instance variables are all strong references. */ + + /** |composedString_| stores the current preedit text */ + NSMutableAttributedString *composedString_; + + /** |originalString_| stores original key strokes. */ + NSMutableString *originalString_; + + /** |cursorPosition_| is the position of cursor in the preedit. If no cursor is found, its value + * should be -1. */ + int cursorPosition_; + + /** |mode_| stores the current input mode (Direct or conversion). */ + mozc::commands::CompositionMode mode_; + + /** |yenSignCharacter_| holds the character for the YEN_SIGN key in JIS keyboard. This config is + * separated from |keyCodeMap_| because it is for DIRECT mode. */ + mozc::config::Config::YenSignCharacter yenSignCharacter_; + + /** |suppressSuggestion_| indicates whether to suppress the suggestion. */ + bool suppressSuggestion_; + + /** |keyCodeMap_| manages the mapping between Mac key code and mozc key events. */ + KeyCodeMap *keyCodeMap_; + + /** |clientBundle_| is the Bundle ID of the client application which the controller communicates + * with. */ + std::string clientBundle_; + + NSRange replacementRange_; + + /** |lastKeyDownTime_| and |lastKeyCode_| are used to handle double tapping. */ + NSTimeInterval lastKeyDownTime_; + uint16_t lastKeyCode_; + + /** |mozcRenderer_| controls the candidate windows. */ + std::unique_ptr mozcRenderer_; + + /** |rendererCommand_| stores the command sent to |mozcRenderer_| */ + mozc::commands::RendererCommand rendererCommand_; + + /** |mozcClient_| manages connection to the mozc server. */ + std::unique_ptr mozcClient_; + + /** |imkClientForTest_| holds the reference to the client object for unit test. */ + id imkClientForTest_; + + /** |menu_| is the NSMenu to be shown in the pulldown menu-list of the IME. */ + IBOutlet NSMenu *menu_; +} + +/** These are externally accessible to achieve tests. */ +@property(readonly) mozc::client::ClientInterface *mozcClient; +@property(readwrite, retain, nonatomic) KeyCodeMap *keyCodeMap; +@property(readonly) mozc::renderer::RendererInterface *renderer; +@property(readonly) mozc::config::Config::YenSignCharacter yenSignCharacter; +@property(readwrite, assign) mozc::commands::CompositionMode mode; +@property(readonly) const mozc::commands::RendererCommand &rendererCommand; +@property(readwrite, assign) NSRange replacementRange; +@property(readwrite, retain) id imkClientForTest; + +/** Sets the RendererReceiver used by all instances of the controller. + * the RendererReceiver is a singleton object used as a proxy to receive messages from + * the renderer process and propage it to the active controller instance. + * + * @param rendererReceiver The RendererReceiver object referred by all instances of the controller. + */ ++ (void)setGlobalRendererReceiver:(RendererReceiver *)rendererReceiver; + +/** sendCommand: is called to send SessionCommand to the server from the renderer, when the user + * clicks a candidate item in candidate windows or when the renderer sends the usage stats event + * information. + * + * @param command The protobuf of command sent to the server. + */ +- (void)sendCommand:(const mozc::commands::SessionCommand &)command; + +/** reconversionClicked: is called when the user clicks "Reconversion" menu item. + * + * @param sender The sender of this request (unused). + */ +- (IBAction)reconversionClicked:(id)sender; + +/** configClicked: is called when the user clicks "Configure Mozc..." menu item. + * + * @param sender The sender of this request (unused). + */ +- (IBAction)configClicked:(id)sender; + +/** dictionaryToolClicked: is called when the user clicks "Dictionary Tool..." menu item. + * + * @param sender The sender of this request (unused). + */ +- (IBAction)dictionaryToolClicked:(id)sender; + +/** registerWordClicked: is called when the user clicks "Add a word..." menu item. + * + * @param sender The sender of this request (unused). + */ +- (IBAction)registerWordClicked:(id)sender; + +/** aboutDialogClicked: is called when the user clicks "About Mozc..." menu item. + * + * @param sender The sender of this request (unused). + */ +- (IBAction)aboutDialogClicked:(id)sender; + +/** outputResult: put result text in the specified |output| into the client application. + * + * @param output The protobuf of the output data. + */ +- (void)outputResult:(mozc::commands::Output *)output; + +/** Sets the ClientInterface to use in the controller. + * + * @param newMozcClient The client object to communicate with the Mozc server process. + */ +- (void)setMozcClient:(std::unique_ptr)newMozcClient; + +/** Sets the RendererInterface to use in the controller. + * + * @param newRenderer The client object to communicate with the candidate renderer process. + */ +- (void)setRenderer:(std::unique_ptr)newRenderer; + +/** Updates |composedString_| from the result of a key event and puts the updated composed string to + * the client application. + * + * @param preedit The protobuf data representing the composed string. + */ +- (void)updateComposedString:(const mozc::commands::Preedit *)preedit; + +/** Updates |candidates_| from the result of a key event. + * + * @param output The protobuf data that contains the data of candidate words. + */ +- (void)updateCandidates:(const mozc::commands::Output *)output; + +/** Clears all candidate data in |candidates_|. */ +- (void)clearCandidates; + +/** Opens a link specified by the URL. + * + * @param url The URL information. + */ +- (void)openLink:(NSURL *)url; + +/** Switches to a new mode and sync the current mode with the converter. + * + * @param new_mode The protobuf enum of the new input mode. + * @param sender The sender of this request. + */ +- (void)switchMode:(mozc::commands::CompositionMode)new_mode client:(id)sender; + +/** Switches the mode icon in the task bar according to |mode_|. */ +- (void)switchDisplayMode; + +/** Commits the specified text to the current client. + * + * @param text The text to be committed. + * @param sender The sender of this request. + */ +- (void)commitText:(const char *)text client:(id)sender; + +/** Conducts the reconvert event. It could have several tricks such like invoking UNDO instead if + * nothing is selected. |sender| has to be the proxy object to the client application, which might + * not be same as the sender of the click event itself when the user clicks the menu item. + * + * @param command The protobuf data to send to the Mozc server process. + * @param sender The sender of this request. + */ +- (void)invokeReconvert:(const mozc::commands::SessionCommand *)command client:(id)sender; + +/** Conducts the undo command. + * + * @param sender The sender of this request. + */ +- (void)invokeUndo:(id)sender; + +/** Processes output fields such as preedit, output text, candidates, and modes and calls methods + * above. + * + * @param output The protobuf of the output data. + * @param sender The sender of this request. + */ +- (void)processOutput:(const mozc::commands::Output *)output client:(id)sender; + +/** Obtains the current configuration from the server and update client-specific configurations. */ +- (void)handleConfig; + +/** Sets up the client capability */ +- (void)setupCapability; + +/** Sets up the client bundle for the sender. + * + * @param sender The sender of this request. + */ +- (void)setupClientBundle:(id)sender; + +/** Launches the word register tool with the current selection range. + * + * @param client The host application. The selection data in this client is used as an input. + */ +- (void)launchWordRegisterTool:(id)client; + +/** Fills the surrounding context (preceding_text and following_text). Returns false if fails to get + * the surrounding context from the client. + * + * @param context The protobuf data used to be fill with the context information. + * @param client The host application as the source of surrounding context. + * @return YES if context was filled; NO otherwise. + */ +- (BOOL)fillSurroundingContext:(mozc::commands::Context *)context client:(id)client; + +@end + +/** GoogleJapaneseInputController is an alias of MozcImkInputController for backward compatibility. + * This will be removed in the future when all clients are migrated to the new name and performed + * relogin at least once. + */ +@interface GoogleJapaneseInputController : MozcImkInputController {} +@end diff --git a/src/mac/GoogleJapaneseInputController.mm b/src/mac/mozc_imk_input_controller.mm similarity index 95% rename from src/mac/GoogleJapaneseInputController.mm rename to src/mac/mozc_imk_input_controller.mm index 2cbf67cc5..14e5dd6ad 100644 --- a/src/mac/GoogleJapaneseInputController.mm +++ b/src/mac/mozc_imk_input_controller.mm @@ -27,7 +27,7 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -#import "mac/GoogleJapaneseInputController.h" +#import "mac/mozc_imk_input_controller.h" #import #import @@ -46,9 +46,8 @@ #include #include -#import "mac/GoogleJapaneseInputControllerInterface.h" -#import "mac/GoogleJapaneseInputServer.h" #import "mac/KeyCodeMap.h" +#import "mac/renderer_receiver.h" #include "absl/log/log.h" #include "absl/strings/string_view.h" @@ -77,6 +76,10 @@ using SetOfString = std::set>; namespace { +// Global object used as a singleton used as a proxy to receive messages from +// the renderer process. +RendererReceiver *gRendererReceiver = nil; + // TODO(horo): This value should be get from system configuration. // DoubleClickInterval can be get from NSEvent (MacOSX ver >= 10.6) constexpr NSTimeInterval kDoubleTapInterval = 0.5; @@ -175,7 +178,7 @@ bool CanSurroundingText(absl::string_view bundle_id) { } } // namespace -@implementation GoogleJapaneseInputController +@implementation MozcImkInputController #pragma mark accessors for testing @synthesize keyCodeMap = keyCodeMap_; @synthesize yenSignCharacter = yenSignCharacter_; @@ -215,7 +218,6 @@ - (id)initWithServer:(IMKServer *)server delegate:(id)delegate client:(id)inputC yenSignCharacter_ = mozc::config::Config::YEN_SIGN; mozcRenderer_ = std::make_unique(); mozcClient_ = mozc::client::ClientFactory::NewClient(); - imkServer_ = reinterpret_cast>(server); imkClientForTest_ = nil; lastKeyDownTime_ = 0; lastKeyCode_ = 0; @@ -280,11 +282,8 @@ - (void)activateServer:(id)sender { } [self handleConfig]; - // This is a workaroud due to the crash issue on macOS 15. - NSOperatingSystemVersion versionInfo = [[NSProcessInfo processInfo] operatingSystemVersion]; - if (versionInfo.majorVersion < 15) { - [imkServer_ setCurrentController:self]; - } + // Sets this controller as the active controller to receive messages from the renderer process. + [gRendererReceiver setCurrentController:self]; std::string window_name, window_owner; if (mozc::MacUtil::GetFrontmostWindowNameAndOwner(&window_name, &window_owner)) { @@ -391,38 +390,46 @@ - (void)switchModeToDirect:(id)sender { } } -// change the mode to the new mode and turn-on the IME if necessary. -- (void)switchModeInternal:(CompositionMode)new_mode { - if (mode_ == mozc::commands::DIRECT) { - // Input mode changes from direct to an active mode. - DLOG(INFO) << "Mode switch: DIRECT -> HIRAGANA, KATAKANA, etc."; +- (void)switchMode:(CompositionMode)new_mode client:(id)sender { + if (mode_ == new_mode) { + return; + } + + if (new_mode == mozc::commands::DIRECT) { + // Turn off the IME and commit the composing text. + DLOG(INFO) << "Mode switch: HIRAGANA, KATAKANA, etc. -> DIRECT"; KeyEvent keyEvent; Output output; - keyEvent.set_special_key(mozc::commands::KeyEvent::ON); + keyEvent.set_special_key(mozc::commands::KeyEvent::OFF); mozcClient_->SendKey(keyEvent, &output); + if (output.has_result()) { + [self commitText:output.result().value().c_str() client:sender]; + } + if ([composedString_ length] > 0) { + [self updateComposedString:nullptr]; + [self clearCandidates]; + } + mode_ = mozc::commands::DIRECT; + return; } - if (mode_ != new_mode) { - // Switch input mode. - DLOG(INFO) << "Switch input mode."; - SessionCommand command; - command.set_type(mozc::commands::SessionCommand::SWITCH_INPUT_MODE); - command.set_composition_mode(new_mode); + if (mode_ == mozc::commands::DIRECT) { + // Turn on the IME as the input mode is changed from DIRECT to an active mode. + DLOG(INFO) << "Mode switch: DIRECT -> HIRAGANA, KATAKANA, etc."; + KeyEvent keyEvent; Output output; - mozcClient_->SendCommand(command, &output); - mode_ = new_mode; + keyEvent.set_special_key(mozc::commands::KeyEvent::ON); + mozcClient_->SendKey(keyEvent, &output); } -} -- (void)switchMode:(CompositionMode)new_mode client:(id)sender { - if (mode_ == new_mode) { - return; - } - if (mode_ != mozc::commands::DIRECT && new_mode == mozc::commands::DIRECT) { - [self switchModeToDirect:sender]; - } else if (new_mode != mozc::commands::DIRECT) { - [self switchModeInternal:new_mode]; - } + // Switch input mode. + DLOG(INFO) << "Switch input mode."; + SessionCommand command; + command.set_type(mozc::commands::SessionCommand::SWITCH_INPUT_MODE); + command.set_composition_mode(new_mode); + Output output; + mozcClient_->SendCommand(command, &output); + mode_ = new_mode; } - (void)switchDisplayMode { @@ -614,10 +621,10 @@ - (void)processOutput:(const mozc::commands::Output *)output client:(id)sender { #pragma mark Mozc Server methods #pragma mark IMKServerInput Protocol -// Currently GoogleJapaneseInputController uses handleEvent:client: +// Currently MozcImkInputController uses handleEvent:client: // method to handle key events. It does not support inputText:client: // nor inputText:key:modifiers:client:. -// Because GoogleJapaneseInputController does not use IMKCandidates, +// Because MozcImkInputController does not use IMKCandidates, // the following methods are not needed to implement: // candidates // @@ -955,4 +962,12 @@ - (void)outputResult:(mozc::commands::Output *)output { } [self commitText:output->result().value().c_str() client:[self client]]; } + ++ (void)setGlobalRendererReceiver:(RendererReceiver *)rendererReceiver { + gRendererReceiver = rendererReceiver; +} +@end + +// An alias of MozcImkInputController for backward compatibility. +@implementation GoogleJapaneseInputController @end diff --git a/src/mac/GoogleJapaneseInputController_test.mm b/src/mac/mozc_imk_input_controller_test.mm similarity index 94% rename from src/mac/GoogleJapaneseInputController_test.mm rename to src/mac/mozc_imk_input_controller_test.mm index 6298cbed1..71e2de228 100644 --- a/src/mac/GoogleJapaneseInputController_test.mm +++ b/src/mac/mozc_imk_input_controller_test.mm @@ -27,12 +27,11 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -#import "mac/GoogleJapaneseInputController.h" +#import "mac/mozc_imk_input_controller.h" #import #import -#import "mac/GoogleJapaneseInputControllerInterface.h" #import "mac/KeyCodeMap.h" #include @@ -238,7 +237,7 @@ bool IsAvailable() const override { commands::RendererCommand called_command_; }; -class GoogleJapaneseInputControllerTest : public testing::Test { +class MozcImkInputControllerTest : public testing::Test { protected: void SetUp() override { mock_server_ = [[MockIMKServer alloc] init]; @@ -262,7 +261,7 @@ void TearDown() override { } void SetUpController() { - controller_ = [[GoogleJapaneseInputController alloc] initWithServer:mock_server_ + controller_ = [[MozcImkInputController alloc] initWithServer:mock_server_ delegate:nil client:mock_client_]; controller_.imkClientForTest = mock_client_; @@ -285,7 +284,7 @@ void ResetClientBundleIdentifier(NSString *new_bundle_id) { MockClient *mock_client_; const MockRenderer *mock_renderer_; - GoogleJapaneseInputController *controller_; + MozcImkInputController *controller_; private: MockIMKServer *mock_server_; @@ -369,7 +368,7 @@ NSTimeInterval GetDoubleTapInterval() { return kDoubleTapInterval; } -BOOL SendKeyEvent(unsigned short keyCode, GoogleJapaneseInputController *controller, +BOOL SendKeyEvent(unsigned short keyCode, MozcImkInputController *controller, MockClient *client) { // tap Kana-key NSEvent *kanaKeyEvent = [NSEvent keyEventWithType:NSEventTypeKeyDown @@ -385,7 +384,7 @@ BOOL SendKeyEvent(unsigned short keyCode, GoogleJapaneseInputController *control return [controller handleEvent:kanaKeyEvent client:client]; } -TEST_F(GoogleJapaneseInputControllerTest, UpdateComposedString) { +TEST_F(MozcImkInputControllerTest, UpdateComposedString) { // If preedit is nullptr, it still calls setMarkedText, with an empty string. NSMutableAttributedString *expected = [[NSMutableAttributedString alloc] initWithString:@""]; [controller_ updateComposedString:nullptr]; @@ -424,14 +423,14 @@ BOOL SendKeyEvent(unsigned short keyCode, GoogleJapaneseInputController *control << [[NSString stringWithFormat:@"expected:%@ actual:%@", expected, actual] UTF8String]; } -TEST_F(GoogleJapaneseInputControllerTest, ClearCandidates) { +TEST_F(MozcImkInputControllerTest, ClearCandidates) { [controller_ clearCandidates]; EXPECT_EQ(mock_renderer_->counter_ExecCommand(), 1); // After clearing candidates, the candidate window has to be invisible. EXPECT_FALSE(mock_renderer_->CalledCommand().visible()); } -TEST_F(GoogleJapaneseInputControllerTest, UpdateCandidates) { +TEST_F(MozcImkInputControllerTest, UpdateCandidates) { // When output is null, same as ClearCandidate [controller_ updateCandidates:nullptr]; // Run the runloop so "delayedUpdateCandidates" can be called @@ -491,7 +490,7 @@ BOOL SendKeyEvent(unsigned short keyCode, GoogleJapaneseInputController *control EXPECT_FALSE(rendererCommand.visible()); } -TEST_F(GoogleJapaneseInputControllerTest, OpenLink) { +TEST_F(MozcImkInputControllerTest, OpenLink) { EXPECT_EQ(gOpenURLCount, 0); [controller_ openLink:[NSURL URLWithString:@"http://www.example.com/"]]; // openURL is invoked @@ -505,7 +504,7 @@ BOOL SendKeyEvent(unsigned short keyCode, GoogleJapaneseInputController *control EXPECT_EQ(gOpenURLCount, 1); } -TEST_F(GoogleJapaneseInputControllerTest, SwitchModeToDirect) { +TEST_F(MozcImkInputControllerTest, SwitchModeToDirect) { // setup the IME status controller_.mode = commands::HIRAGANA; commands::Preedit preedit; @@ -521,7 +520,7 @@ BOOL SendKeyEvent(unsigned short keyCode, GoogleJapaneseInputController *control SendKeyWithContext(HasSpecialKey(commands::KeyEvent::OFF), _, NotNull())) .WillOnce(DoAll(SetArgPointee<2>(output), Return(true))); - [controller_ switchModeToDirect:mock_client_]; + [controller_ switchMode:commands::DIRECT client:mock_client_]; EXPECT_EQ(controller_.mode, commands::DIRECT); EXPECT_EQ([mock_client_ getCounter:"insertText:replacementRange:"], 1); EXPECT_TRUE([@"foo" isEqualToString:mock_client_.insertedText]); @@ -529,7 +528,7 @@ BOOL SendKeyEvent(unsigned short keyCode, GoogleJapaneseInputController *control EXPECT_FALSE(controller_.rendererCommand.visible()); } -TEST_F(GoogleJapaneseInputControllerTest, SwitchModeInternal) { +TEST_F(MozcImkInputControllerTest, SwitchMode) { // When a mode changes from DIRECT, it should invoke "ON" command beforehand. EXPECT_CALL(*mock_mozc_client_, SendKeyWithContext(HasSpecialKey(commands::KeyEvent::ON), _, NotNull())) @@ -540,25 +539,25 @@ BOOL SendKeyEvent(unsigned short keyCode, GoogleJapaneseInputController *control .WillRepeatedly(DoAll(SaveArg<0>(&actual_command), Return(true))); controller_.mode = commands::DIRECT; - [controller_ switchModeInternal:commands::HIRAGANA]; + [controller_ switchMode:commands::HIRAGANA client:mock_client_]; EXPECT_EQ(controller_.mode, commands::HIRAGANA); EXPECT_THAT(actual_command, Type(commands::SessionCommand::SWITCH_INPUT_MODE)); EXPECT_THAT(actual_command, CompositionMode(commands::HIRAGANA)); // Switch from HIRAGANA to KATAKANA. Just sending mode switching command. controller_.mode = commands::HIRAGANA; - [controller_ switchModeInternal:commands::HALF_KATAKANA]; + [controller_ switchMode:commands::HALF_KATAKANA client:mock_client_]; EXPECT_EQ(controller_.mode, commands::HALF_KATAKANA); EXPECT_THAT(actual_command, Type(commands::SessionCommand::SWITCH_INPUT_MODE)); EXPECT_THAT(actual_command, CompositionMode(commands::HALF_KATAKANA)); Mock::VerifyAndClearExpectations(&mock_mozc_client_); // going to same mode does not cause sendcommand - [controller_ switchModeInternal:commands::HALF_KATAKANA]; + [controller_ switchMode:commands::HALF_KATAKANA client:mock_client_]; EXPECT_EQ(controller_.mode, commands::HALF_KATAKANA); } -TEST_F(GoogleJapaneseInputControllerTest, SwitchDisplayMode) { +TEST_F(MozcImkInputControllerTest, SwitchDisplayMode) { EXPECT_TRUE(mock_client_.selectedMode.empty()); EXPECT_EQ(controller_.mode, commands::DIRECT); [controller_ switchDisplayMode]; @@ -566,7 +565,7 @@ BOOL SendKeyEvent(unsigned short keyCode, GoogleJapaneseInputController *control EXPECT_EQ(mock_client_.selectedMode, "com.apple.inputmethod.Roman"); // Does not change the display mode for MS Word. See - // GoogleJapaneseInputController.mm for the detailed information. + // MozcImkInputController.mm for the detailed information. ResetClientBundleIdentifier(@"com.microsoft.Word"); [controller_ switchMode:commands::HIRAGANA client:mock_client_]; EXPECT_EQ(controller_.mode, commands::HIRAGANA); @@ -576,7 +575,7 @@ BOOL SendKeyEvent(unsigned short keyCode, GoogleJapaneseInputController *control EXPECT_EQ(mock_client_.selectedMode, "com.apple.inputmethod.Roman"); } -TEST_F(GoogleJapaneseInputControllerTest, commitText) { +TEST_F(MozcImkInputControllerTest, commitText) { controller_.replacementRange = NSMakeRange(0, 1); [controller_ commitText:"foo" client:mock_client_]; @@ -586,7 +585,7 @@ BOOL SendKeyEvent(unsigned short keyCode, GoogleJapaneseInputController *control EXPECT_EQ([controller_ replacementRange].location, NSNotFound); } -TEST_F(GoogleJapaneseInputControllerTest, handleConfig) { +TEST_F(MozcImkInputControllerTest, handleConfig) { // Does not support multiple-calculation config::Config config; config.set_preedit_method(config::Config::KANA); @@ -603,7 +602,7 @@ BOOL SendKeyEvent(unsigned short keyCode, GoogleJapaneseInputController *control << [mock_client_.overriddenLayout UTF8String]; } -TEST_F(GoogleJapaneseInputControllerTest, DoubleTapKanaReconvert) { +TEST_F(MozcImkInputControllerTest, DoubleTapKanaReconvert) { // tap (short) tap -> emit undo command controller_.mode = commands::HIRAGANA; @@ -630,7 +629,7 @@ BOOL SendKeyEvent(unsigned short keyCode, GoogleJapaneseInputController *control EXPECT_THAT(actual_command, Text("bcd")); } -TEST_F(GoogleJapaneseInputControllerTest, DoubleTapKanaUndo) { +TEST_F(MozcImkInputControllerTest, DoubleTapKanaUndo) { // tap (short) tap -> emit undo command controller_.mode = commands::HIRAGANA; @@ -653,7 +652,7 @@ BOOL SendKeyEvent(unsigned short keyCode, GoogleJapaneseInputController *control EXPECT_EQ(SendKeyEvent(kVK_JIS_Kana, controller_, mock_client_), YES); } -TEST_F(GoogleJapaneseInputControllerTest, DoubleTapKanaUndoTimeOver) { +TEST_F(MozcImkInputControllerTest, DoubleTapKanaUndoTimeOver) { // tap (long) tap -> don't emit undo command controller_.mode = commands::HIRAGANA; @@ -671,7 +670,7 @@ BOOL SendKeyEvent(unsigned short keyCode, GoogleJapaneseInputController *control EXPECT_EQ(SendKeyEvent(kVK_JIS_Kana, controller_, mock_client_), YES); } -TEST_F(GoogleJapaneseInputControllerTest, SingleAndDoubleTapKanaUndo) { +TEST_F(MozcImkInputControllerTest, SingleAndDoubleTapKanaUndo) { // tap (long) tap (short) tap -> emit once controller_.mode = commands::HIRAGANA; @@ -700,7 +699,7 @@ BOOL SendKeyEvent(unsigned short keyCode, GoogleJapaneseInputController *control EXPECT_EQ(SendKeyEvent(kVK_JIS_Kana, controller_, mock_client_), YES); } -TEST_F(GoogleJapaneseInputControllerTest, TripleTapKanaUndo) { +TEST_F(MozcImkInputControllerTest, TripleTapKanaUndo) { // tap (short) tap (short) tap -> emit twice controller_.mode = commands::HIRAGANA; @@ -730,7 +729,7 @@ BOOL SendKeyEvent(unsigned short keyCode, GoogleJapaneseInputController *control EXPECT_EQ(SendKeyEvent(kVK_JIS_Kana, controller_, mock_client_), YES); } -TEST_F(GoogleJapaneseInputControllerTest, QuadrupleTapKanaUndo) { +TEST_F(MozcImkInputControllerTest, QuadrupleTapKanaUndo) { // tap (short) tap (short) tap (short) tap -> emit thrice controller_.mode = commands::HIRAGANA; @@ -765,7 +764,7 @@ BOOL SendKeyEvent(unsigned short keyCode, GoogleJapaneseInputController *control EXPECT_EQ(SendKeyEvent(kVK_JIS_Kana, controller_, mock_client_), YES); } -TEST_F(GoogleJapaneseInputControllerTest, DoubleTapEisuCommitRawText) { +TEST_F(MozcImkInputControllerTest, DoubleTapEisuCommitRawText) { // Send Eisu-key. // Because of special hack for Eisu/Kana keys, it returns YES. EXPECT_EQ(SendKeyEvent(kVK_JIS_Eisu, controller_, mock_client_), YES); @@ -781,7 +780,7 @@ BOOL SendKeyEvent(unsigned short keyCode, GoogleJapaneseInputController *control EXPECT_EQ(SendKeyEvent(kVK_JIS_Eisu, controller_, mock_client_), YES); } -TEST_F(GoogleJapaneseInputControllerTest, fillSurroundingContext) { +TEST_F(MozcImkInputControllerTest, fillSurroundingContext) { [mock_client_ setAttributedString:[[NSAttributedString alloc] initWithString:@"abcde"]]; mock_client_.expectedRange = NSMakeRange(2, 1); commands::Context context; diff --git a/src/mac/GoogleJapaneseInputServer.h b/src/mac/renderer_receiver.h similarity index 75% rename from src/mac/GoogleJapaneseInputServer.h rename to src/mac/renderer_receiver.h index 63b06a5bd..e1d45f985 100644 --- a/src/mac/GoogleJapaneseInputServer.h +++ b/src/mac/renderer_receiver.h @@ -27,20 +27,22 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#ifndef MOZC_MAC_RENDERER_RECEIVER_H_ +#define MOZC_MAC_RENDERER_RECEIVER_H_ + #import -#import #import "mac/common.h" -// GoogleJapaneseInputServer is a subclass of IMKServer but implements -// RendererCallback, so it can accept user's click event. -@interface GoogleJapaneseInputServer : IMKServer { - // The controller which accepts user's clicks - id current_controller_; - // NSConnection to communicate with the renderer process - NSConnection *renderer_conection_; -} +/** RendererReceiver is a class to receive messages from the renderer process. */ +@interface RendererReceiver : NSObject + +/** Initializes the receiver with the given connection name. + * + * @param name The connection name (e.g. "Mozc_Renderer_Connection"). + */ +- (id)initWithName:(NSString *)name; -// Register the NSConnection for the renderer process -- (BOOL)registerRendererConnection; @end + +#endif // MOZC_MAC_RENDERER_RECEIVER_H_ diff --git a/src/mac/GoogleJapaneseInputServer.mm b/src/mac/renderer_receiver.mm similarity index 65% rename from src/mac/GoogleJapaneseInputServer.mm rename to src/mac/renderer_receiver.mm index 39344ad63..cf20e3906 100644 --- a/src/mac/GoogleJapaneseInputServer.mm +++ b/src/mac/renderer_receiver.mm @@ -27,24 +27,36 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -#import "mac/GoogleJapaneseInputServer.h" +#import "mac/renderer_receiver.h" -#include - -#include "absl/log/log.h" -#include "base/const.h" #include "protocol/commands.pb.h" -@implementation GoogleJapaneseInputServer -- (BOOL)registerRendererConnection { - NSString *connectionName = @kProductPrefix "_Renderer_Connection"; - renderer_conection_ = [[NSConnection alloc] init]; - [renderer_conection_ setRootObject:self]; - return [renderer_conection_ registerName:connectionName]; +@implementation RendererReceiver { + /** The current active controller that handles events from the renderer process. */ + id _currentController; + + /** NSConnection to communicate with the renderer process. */ + NSConnection *_rendererConnection; } +- (id)initWithName:(NSString *)name { + self = [super init]; + if (self) { + // _rendererConnection receives IPC calls from the renderer process. + // See: renderer/mac/mac_server_send_command.mm + _rendererConnection = [[NSConnection alloc] init]; + [_rendererConnection setRootObject:self]; + [_rendererConnection registerName:name]; + } + return self; +} + +#pragma mark ServerCallback +// Methods inherited from the ServerCallback protocol (see: common.h). + +// sendData is a method of the ServerCallback protocol. - (void)sendData:(NSData *)data { - if (current_controller_ == nil) { + if (!_currentController) { return; } @@ -53,10 +65,10 @@ - (void)sendData:(NSData *)data { if (!command.ParseFromArray([data bytes], length)) { return; } - - [current_controller_ sendCommand:command]; + [_currentController sendCommand:command]; } +// outputResult is a method of the ServerCallback protocol. - (void)outputResult:(NSData *)data { mozc::commands::Output output; int32_t length = static_cast([data length]); @@ -64,10 +76,11 @@ - (void)outputResult:(NSData *)data { return; } - [current_controller_ outputResult:&output]; + [_currentController outputResult:&output]; } +// setCurrentController is a method of the ServerCallback protocol. - (void)setCurrentController:(id)controller { - current_controller_ = controller; + _currentController = controller; } @end diff --git a/src/mac/GoogleJapaneseInputServer_test.mm b/src/mac/renderer_receiver_test.mm similarity index 56% rename from src/mac/GoogleJapaneseInputServer_test.mm rename to src/mac/renderer_receiver_test.mm index b9253592b..45bf2c100 100644 --- a/src/mac/GoogleJapaneseInputServer_test.mm +++ b/src/mac/renderer_receiver_test.mm @@ -27,88 +27,74 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -#import "mac/GoogleJapaneseInputServer.h" +#import "mac/renderer_receiver.h" #include "protocol/commands.pb.h" -#include "testing/googletest.h" #include "testing/gunit.h" -class GoogleJapaneseInputServerTest : public testing::Test { +class RendererReceiverTest : public testing::Test { protected: void SetUp() { - // Although GoogleJapaneseInputServer is a subclass of IMKServer, - // it does not use initWithName:... method to instantiate the - // object because we don't test those IMKServer functionality - // during this test. - server_ = [[GoogleJapaneseInputServer alloc] init]; + // Initialize RendererReceiver with init rather than initWithName. + // This is because the test does not use renderer_connection_. + _receiver = [[RendererReceiver alloc] init]; } - protected: - GoogleJapaneseInputServer *server_; + RendererReceiver *_receiver; }; @interface MockController : NSObject { - int numSendData_; - mozc::commands::SessionCommand *expectedCommand_; - int numOutputResult_; - mozc::commands::Output *expectedData_; + int _numSendCommand; + mozc::commands::SessionCommand _receivedSessionCommand; + int _numOutputResult; + mozc::commands::Output _receivedOutput; } -@property(readonly) int numSendData; -@property(readwrite, assign) mozc::commands::SessionCommand *expectedCommand; +@property(readonly) int numSendCommand; +@property(readonly) mozc::commands::SessionCommand receivedSessionCommand; @property(readonly) int numOutputResult; -@property(readwrite, assign) mozc::commands::Output *expectedData; - -- (void)sendCommand:(mozc::commands::SessionCommand &)command; -- (void)outputResult:(mozc::commands::Output *)data; +@property(readonly) mozc::commands::Output receivedOutput; @end @implementation MockController -@synthesize numSendData = numSendData_; -@synthesize expectedCommand = expectedCommand_; -@synthesize numOutputResult = numOutputResult_; -@synthesize expectedData = expectedData_; -- (void)sendCommand:(mozc::commands::SessionCommand &)command { - ASSERT_NE((void *)0, expectedCommand_); - EXPECT_EQ(command.DebugString(), expectedCommand_->DebugString()); - ++numSendData_; +- (void)sendCommand:(mozc::commands::SessionCommand &)data { + _receivedSessionCommand = data; + ++_numSendCommand; } - (void)outputResult:(mozc::commands::Output *)data { - ASSERT_NE(data, (void *)0); - ASSERT_NE(expectedData_, (void *)0); - EXPECT_EQ(data->DebugString(), expectedData_->DebugString()); - ++numOutputResult_; + _receivedOutput = *data; + ++_numOutputResult; } @end -TEST_F(GoogleJapaneseInputServerTest, sendData) { +TEST_F(RendererReceiverTest, sendData) { MockController *controller = [[MockController alloc] init]; - [server_ setCurrentController:controller]; + [_receiver setCurrentController:controller]; mozc::commands::SessionCommand command; command.Clear(); command.set_type(mozc::commands::SessionCommand::SELECT_CANDIDATE); command.set_id(0); - controller.expectedCommand = &command; std::string commandData = command.SerializeAsString(); - [server_ sendData:[NSData dataWithBytes:commandData.data() length:commandData.size()]]; - EXPECT_EQ(controller.numSendData, 1); + [_receiver sendData:[NSData dataWithBytes:commandData.data() length:commandData.size()]]; + EXPECT_EQ(controller.numSendCommand, 1); + EXPECT_EQ(controller.receivedSessionCommand.DebugString(), command.DebugString()); } -TEST_F(GoogleJapaneseInputServerTest, outputResult) { +TEST_F(RendererReceiverTest, outputResult) { MockController *controller = [[MockController alloc] init]; - [server_ setCurrentController:controller]; + [_receiver setCurrentController:controller]; mozc::commands::Output output; output.Clear(); output.mutable_result()->set_type(mozc::commands::Result::STRING); output.mutable_result()->set_key("foobar"); output.mutable_result()->set_value("baz"); - controller.expectedData = &output; std::string outputData = output.SerializeAsString(); - [server_ outputResult:[NSData dataWithBytes:outputData.data() length:outputData.size()]]; + [_receiver outputResult:[NSData dataWithBytes:outputData.data() length:outputData.size()]]; EXPECT_EQ(controller.numOutputResult, 1); + EXPECT_EQ(controller.receivedOutput.DebugString(), output.DebugString()); } diff --git a/src/protocol/commands.proto b/src/protocol/commands.proto index 62d28e7fc..9ff401be0 100644 --- a/src/protocol/commands.proto +++ b/src/protocol/commands.proto @@ -573,7 +573,7 @@ message Capability { [default = NO_TEXT_DELETION_CAPABILITY]; } -// Next ID: 97 +// Next ID: 98 // Bundles together some Android experiment flags so that they can be easily // retrieved throughout the native code. These flags are generally specific to // the decoder, and are made available when the decoder is initialized. diff --git a/src/renderer/BUILD.bazel b/src/renderer/BUILD.bazel index a49541143..6654ab964 100644 --- a/src/renderer/BUILD.bazel +++ b/src/renderer/BUILD.bazel @@ -291,6 +291,7 @@ mozc_objc_library( sdk_frameworks = [ "Carbon", "Cocoa", + "Foundation", ], deps = [ ":renderer_interface", @@ -307,9 +308,9 @@ mozc_objc_library( "//protocol:candidates_cc_proto", "//protocol:commands_cc_proto", "//protocol:renderer_cc_proto", - "@com_google_absl//absl/base", "@com_google_absl//absl/log", "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", "@com_google_absl//absl/synchronization", ], ) diff --git a/src/renderer/mac/CandidateController.mm b/src/renderer/mac/CandidateController.mm index eac336370..4f4993e57 100644 --- a/src/renderer/mac/CandidateController.mm +++ b/src/renderer/mac/CandidateController.mm @@ -202,7 +202,7 @@ int GetBaseScreenHeight() { command_.preedit_rectangle().right() - command_.preedit_rectangle().left(), command_.preedit_rectangle().bottom() - command_.preedit_rectangle().top()); // The origin point of command_.preedit_rectangle() is the left-top of the - // base screen which is set in GoogleJapaneseInputController. It is + // base screen which is set in MozcImkInputController. It is // unnecessary calculation but to support older version of GoogleJapaneseInput // process we should not change it. So we minus the height of the screen here. mozc::Rect preedit_rect(mozc::Point(command_.preedit_rectangle().left(), @@ -211,7 +211,7 @@ int GetBaseScreenHeight() { // This is a hacky way to check vertical writing. // TODO(komatsu): We should use the return value of attributesForCharacterIndex - // in GoogleJapaneseInputController.mm as a proper way. + // in MozcImkInputController as a proper way. const bool is_vertical = (preedit_size.height < preedit_size.width); // Expand the rect size to make a margin to the candidate window. diff --git a/src/renderer/mac/CandidateView.h b/src/renderer/mac/CandidateView.h index abe5b7ed3..e40a02142 100644 --- a/src/renderer/mac/CandidateView.h +++ b/src/renderer/mac/CandidateView.h @@ -54,26 +54,12 @@ enum COLUMN_TYPE { // CandidateView is an NSView subclass to draw the candidate window // according to the current candidates. -@interface CandidateView : NSView { - @private - mozc::commands::Candidates candidates_; - mozc::renderer::TableLayout *tableLayout_; - const mozc::renderer::RendererStyle *style_; - - // The row which has focused background. - int focusedRow_; - - // Cache of attributed strings which is allocated at updateLayout. - NSArray *candidateStringsCache_; - - // |command_sender_| holds a callback for mouse clicks. - mozc::client::SendCommandInterface *command_sender_; -} +@interface CandidateView : NSView // setCandidates: sets the candidates to be rendered. - (void)setCandidates:(const mozc::commands::Candidates *)candidates; -// setController: sets the reference of GoogleJapaneseInputController. +// setController: sets the reference of MozcImkInputController. // It will be used when mouse clicks. It doesn't take ownerships of // |controller|. - (void)setSendCommandInterface:(mozc::client::SendCommandInterface *)command_sender; diff --git a/src/renderer/mac/CandidateView.mm b/src/renderer/mac/CandidateView.mm index da2993931..6374f6d2a 100644 --- a/src/renderer/mac/CandidateView.mm +++ b/src/renderer/mac/CandidateView.mm @@ -27,12 +27,15 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -#include - #import "renderer/mac/CandidateView.h" -#include "absl/base/call_once.h" +#import + +#include +#include + #include "absl/log/log.h" +#include "absl/strings/str_format.h" #include "client/client_interface.h" #include "protocol/commands.pb.h" #include "protocol/renderer_style.pb.h" @@ -44,7 +47,6 @@ using mozc::commands::Candidates; using mozc::commands::Output; using mozc::commands::SessionCommand; -using mozc::renderer::RendererStyle; using mozc::renderer::RendererStyleHandler; using mozc::renderer::TableLayout; using mozc::renderer::mac::MacViewUtil; @@ -53,48 +55,10 @@ // native candidate window. // TODO(mukai): integrate and share the code among Win and Mac. -namespace { -const NSImage *g_LogoImage = nullptr; -int g_column_minimum_width = 0; -absl::once_flag g_OnceForInitializeStyle; - -void InitializeDefaultStyle() { - RendererStyle style; - RendererStyleHandler::GetRendererStyle(&style); - - std::string logo_file_name = style.logo_file_name(); - g_LogoImage = [NSImage imageNamed:[NSString stringWithUTF8String:logo_file_name.c_str()]]; - if (g_LogoImage) { - // setFlipped is deprecated at Snow Leopard, but we use this because - // it works well with Snow Leopard and new method to deal with - // flipped view doesn't work with Leopard. - [g_LogoImage setFlipped:YES]; - - // Fix the image size. Sometimes the size can be smaller than the - // actual size because of blank margin. - NSArray *logoReps = [g_LogoImage representations]; - if (logoReps && [logoReps count] > 0) { - NSImageRep *representation = [logoReps objectAtIndex:0]; - [g_LogoImage setSize:NSMakeSize([representation pixelsWide], [representation pixelsHigh])]; - } - } - - NSString *nsstr = [NSString stringWithUTF8String:style.column_minimum_width_string().c_str()]; - NSDictionary *attr = [NSDictionary dictionaryWithObject:[NSFont messageFontOfSize:14] - forKey:NSFontAttributeName]; - NSAttributedString *defaultMessage = [[NSAttributedString alloc] initWithString:nsstr - attributes:attr]; - g_column_minimum_width = [defaultMessage size].width; - - // default line width is specified as 1.0 *pt*, but we want to draw - // it as 1.0 px. - [NSBezierPath setDefaultLineWidth:1.0]; - [NSBezierPath setDefaultLineJoinStyle:NSLineJoinStyleMiter]; -} -} // namespace - // Private method declarations. @interface CandidateView () +- (void)initializeDefaultStyle; + // Draw the |row|-th row. - (void)drawRow:(int)row; @@ -105,56 +69,90 @@ - (void)drawFooter; - (void)drawVScrollBar; @end -@implementation CandidateView +@implementation CandidateView { + const NSImage *logoImage_; + int columnMinimumWidth_; + + mozc::commands::Candidates candidates_; + mozc::renderer::TableLayout tableLayout_; + mozc::renderer::RendererStyle style_; + + // The row which has focused background. + int focusedRow_; + + // Cache of attributed strings which is allocated at updateLayout. + NSArray *candidateStringsCache_; + + // |command_sender_| holds a callback for mouse clicks. + mozc::client::SendCommandInterface *command_sender_; +} + #pragma mark initialization - (id)initWithFrame:(NSRect)frame { - absl::call_once(g_OnceForInitializeStyle, &InitializeDefaultStyle); self = [super initWithFrame:frame]; if (self) { - tableLayout_ = new (std::nothrow) TableLayout; - RendererStyle *style = new (std::nothrow) RendererStyle; - if (style) { - RendererStyleHandler::GetRendererStyle(style); - } - style_ = style; + [self initializeDefaultStyle]; focusedRow_ = -1; } - if (!tableLayout_ || !style_) { - self = nil; - } return self; } +- (void)initializeDefaultStyle { + RendererStyleHandler::GetRendererStyle(&style_); + + const std::string &logo_file_name = style_.logo_file_name(); + logoImage_ = [NSImage imageNamed:[NSString stringWithUTF8String:logo_file_name.c_str()]]; + if (logoImage_) { + // Fix the image size. Sometimes the size can be smaller than the + // actual size because of blank margin. + const NSArray *logoReps = [logoImage_ representations]; + if (logoReps && [logoReps count] > 0) { + const NSImageRep *representation = [logoReps objectAtIndex:0]; + [logoImage_ setSize:NSMakeSize([representation pixelsWide], [representation pixelsHigh])]; + } + } + + NSString *nsstr = [NSString stringWithUTF8String:style_.column_minimum_width_string().c_str()]; + NSDictionary *attr = [NSDictionary dictionaryWithObject:[NSFont messageFontOfSize:14] + forKey:NSFontAttributeName]; + const NSAttributedString *defaultMessage = [[NSAttributedString alloc] initWithString:nsstr + attributes:attr]; + columnMinimumWidth_ = [defaultMessage size].width; + + // default line width is specified as 1.0 *pt*, but we want to draw + // it as 1.0 px. + [NSBezierPath setDefaultLineWidth:1.0]; + [NSBezierPath setDefaultLineJoinStyle:NSLineJoinStyleMiter]; +} + - (void)setCandidates:(const Candidates *)candidates { - candidates_.CopyFrom(*candidates); + candidates_ = *candidates; } - (void)setSendCommandInterface:(SendCommandInterface *)command_sender { command_sender_ = command_sender; } +// Override of NSView. - (BOOL)isFlipped { return YES; } - (void)dealloc { candidateStringsCache_ = nil; - delete tableLayout_; - delete style_; } - (const TableLayout *)tableLayout { - return tableLayout_; + return &tableLayout_; } #pragma mark drawing -#define max(x, y) (((x) > (y)) ? (x) : (y)) - (NSSize)updateLayout { candidateStringsCache_ = nil; - tableLayout_->Initialize(candidates_.candidate_size(), NUMBER_OF_COLUMNS); - tableLayout_->SetWindowBorder(style_->window_border()); + tableLayout_.Initialize(candidates_.candidate_size(), NUMBER_OF_COLUMNS); + tableLayout_.SetWindowBorder(style_.window_border()); // calculating focusedRow_ if (candidates_.has_focused_index() && candidates_.candidate_size() > 0) { @@ -171,62 +169,63 @@ - (NSSize)updateLayout { const mozc::commands::Footer &footer = candidates_.footer(); if (footer.has_label()) { - NSAttributedString *footerLabel = - MacViewUtil::ToNSAttributedString(footer.label(), style_->footer_style()); - NSSize footerLabelSize = - MacViewUtil::applyTheme([footerLabel size], style_ -> footer_style()); + const NSAttributedString *footerLabel = + MacViewUtil::ToNSAttributedString(footer.label(), style_.footer_style()); + const NSSize footerLabelSize = + MacViewUtil::applyTheme([footerLabel size], style_.footer_style()); footerSize.width += footerLabelSize.width; - footerSize.height = max(footerSize.height, footerLabelSize.height); + footerSize.height = std::max(footerSize.height, footerLabelSize.height); } if (footer.has_sub_label()) { - NSAttributedString *footerSubLabel = - MacViewUtil::ToNSAttributedString(footer.sub_label(), style_->footer_sub_label_style()); - NSSize footerSubLabelSize = - MacViewUtil::applyTheme([footerSubLabel size], style_ -> footer_sub_label_style()); + const NSAttributedString *footerSubLabel = + MacViewUtil::ToNSAttributedString(footer.sub_label(), style_.footer_sub_label_style()); + const NSSize footerSubLabelSize = + MacViewUtil::applyTheme([footerSubLabel size], style_.footer_sub_label_style()); footerSize.width += footerSubLabelSize.width; - footerSize.height = max(footerSize.height, footerSubLabelSize.height); + footerSize.height = std::max(footerSize.height, footerSubLabelSize.height); } - if (footer.logo_visible() && g_LogoImage) { - NSSize logoSize = [g_LogoImage size]; + if (footer.logo_visible() && logoImage_) { + const NSSize logoSize = [logoImage_ size]; footerSize.width += logoSize.width; - footerSize.height = max(footerSize.height, logoSize.height); + footerSize.height = std::max(footerSize.height, logoSize.height); } if (footer.index_visible()) { const int focusedIndex = candidates_.focused_index(); const int totalItems = candidates_.size(); - NSString *footerIndex = [NSString stringWithFormat:@"%d/%d", focusedIndex + 1, totalItems]; - NSAttributedString *footerAttributedIndex = - MacViewUtil::ToNSAttributedString([footerIndex UTF8String], style_ -> footer_style()); - NSSize footerIndexSize = - MacViewUtil::applyTheme([footerAttributedIndex size], style_ -> footer_style()); + const NSString *footerIndex = + [NSString stringWithFormat:@"%d/%d", focusedIndex + 1, totalItems]; + const NSAttributedString *footerAttributedIndex = + MacViewUtil::ToNSAttributedString([footerIndex UTF8String], style_.footer_style()); + const NSSize footerIndexSize = + MacViewUtil::applyTheme([footerAttributedIndex size], style_.footer_style()); footerSize.width += footerIndexSize.width; - footerSize.height = max(footerSize.height, footerIndexSize.height); + footerSize.height = std::max(footerSize.height, footerIndexSize.height); } - footerSize.height += style_->footer_border_colors_size(); - tableLayout_->EnsureFooterSize(MacViewUtil::ToSize(footerSize)); + footerSize.height += style_.footer_border_colors_size(); + tableLayout_.EnsureFooterSize(MacViewUtil::ToSize(footerSize)); } - tableLayout_->SetRowRectPadding(style_->row_rect_padding()); + tableLayout_.SetRowRectPadding(style_.row_rect_padding()); if (candidates_.candidate_size() < candidates_.size()) { - tableLayout_->SetVScrollBar(style_->scrollbar_width()); + tableLayout_.SetVScrollBar(style_.scrollbar_width()); } - NSAttributedString *gap1 = - MacViewUtil::ToNSAttributedString(" ", style_->text_styles(COLUMN_GAP1)); - tableLayout_->EnsureCellSize(COLUMN_GAP1, MacViewUtil::ToSize([gap1 size])); + const NSAttributedString *gap1 = + MacViewUtil::ToNSAttributedString(" ", style_.text_styles(COLUMN_GAP1)); + tableLayout_.EnsureCellSize(COLUMN_GAP1, MacViewUtil::ToSize([gap1 size])); NSMutableArray *newCache = [[NSMutableArray array] init]; for (size_t i = 0; i < candidates_.candidate_size(); ++i) { const Candidates::Candidate &candidate = candidates_.candidate(i); - NSAttributedString *shortcut = MacViewUtil::ToNSAttributedString( - candidate.annotation().shortcut(), style_->text_styles(COLUMN_SHORTCUT)); + const NSAttributedString *shortcut = MacViewUtil::ToNSAttributedString( + candidate.annotation().shortcut(), style_.text_styles(COLUMN_SHORTCUT)); std::string value = candidate.value(); if (candidate.annotation().has_prefix()) { - value = candidate.annotation().prefix() + value; + value.insert(0, candidate.annotation().prefix()); // Prepend the prefix() to value. } if (candidate.annotation().has_suffix()) { value.append(candidate.annotation().suffix()); @@ -235,35 +234,35 @@ - (NSSize)updateLayout { value.append(" "); } - NSAttributedString *candidateValue = - MacViewUtil::ToNSAttributedString(value, style_->text_styles(COLUMN_CANDIDATE)); - NSAttributedString *description = MacViewUtil::ToNSAttributedString( - candidate.annotation().description(), style_->text_styles(COLUMN_DESCRIPTION)); + const NSAttributedString *candidateValue = + MacViewUtil::ToNSAttributedString(value, style_.text_styles(COLUMN_CANDIDATE)); + const NSAttributedString *description = MacViewUtil::ToNSAttributedString( + candidate.annotation().description(), style_.text_styles(COLUMN_DESCRIPTION)); if ([shortcut length] > 0) { - NSSize shortcutSize = - MacViewUtil::applyTheme([shortcut size], style_ -> text_styles(COLUMN_SHORTCUT)); - tableLayout_->EnsureCellSize(COLUMN_SHORTCUT, MacViewUtil::ToSize(shortcutSize)); + const NSSize shortcutSize = + MacViewUtil::applyTheme([shortcut size], style_.text_styles(COLUMN_SHORTCUT)); + tableLayout_.EnsureCellSize(COLUMN_SHORTCUT, MacViewUtil::ToSize(shortcutSize)); } if ([candidateValue length] > 0) { - NSSize valueSize = - MacViewUtil::applyTheme([candidateValue size], style_ -> text_styles(COLUMN_CANDIDATE)); - tableLayout_->EnsureCellSize(COLUMN_CANDIDATE, MacViewUtil::ToSize(valueSize)); + const NSSize valueSize = + MacViewUtil::applyTheme([candidateValue size], style_.text_styles(COLUMN_CANDIDATE)); + tableLayout_.EnsureCellSize(COLUMN_CANDIDATE, MacViewUtil::ToSize(valueSize)); } if ([description length] > 0) { - NSSize descriptionSize = - MacViewUtil::applyTheme([description size], style_ -> text_styles(COLUMN_DESCRIPTION)); - tableLayout_->EnsureCellSize(COLUMN_DESCRIPTION, MacViewUtil::ToSize(descriptionSize)); + const NSSize descriptionSize = + MacViewUtil::applyTheme([description size], style_.text_styles(COLUMN_DESCRIPTION)); + tableLayout_.EnsureCellSize(COLUMN_DESCRIPTION, MacViewUtil::ToSize(descriptionSize)); } [newCache addObject:[NSArray arrayWithObjects:shortcut, gap1, candidateValue, description, nil]]; } - tableLayout_->EnsureColumnsWidth(COLUMN_CANDIDATE, COLUMN_DESCRIPTION, g_column_minimum_width); + tableLayout_.EnsureColumnsWidth(COLUMN_CANDIDATE, COLUMN_DESCRIPTION, columnMinimumWidth_); candidateStringsCache_ = newCache; - tableLayout_->FreezeLayout(); - return MacViewUtil::ToNSSize(tableLayout_->GetTotalSize()); + tableLayout_.FreezeLayout(); + return MacViewUtil::ToNSSize(tableLayout_.GetTotalSize()); } - (void)drawRect:(NSRect)rect { @@ -282,8 +281,8 @@ - (void)drawRect:(NSRect)rect { [self drawFooter]; // Draw the window border at last - [MacViewUtil::ToNSColor(style_->border_color()) set]; - mozc::Size windowSize = tableLayout_->GetTotalSize(); + [MacViewUtil::ToNSColor(style_.border_color()) set]; + const mozc::Size windowSize = tableLayout_.GetTotalSize(); [NSBezierPath strokeRect:NSMakeRect(0.5, 0.5, windowSize.width - 1, windowSize.height - 1)]; } @@ -292,10 +291,10 @@ - (void)drawRect:(NSRect)rect { - (void)drawRow:(int)row { if (row == focusedRow_) { // Draw focused background - NSRect focusedRect = MacViewUtil::ToNSRect(tableLayout_->GetRowRect(focusedRow_)); - [MacViewUtil::ToNSColor(style_->focused_background_color()) set]; + NSRect focusedRect = MacViewUtil::ToNSRect(tableLayout_.GetRowRect(focusedRow_)); + [MacViewUtil::ToNSColor(style_.focused_background_color()) set]; [NSBezierPath fillRect:focusedRect]; - [MacViewUtil::ToNSColor(style_->focused_border_color()) set]; + [MacViewUtil::ToNSColor(style_.focused_border_color()) set]; // Fix the border position. Because a line should be drawn at the // middle point of the pixel, origin should be shifted by 0.5 unit // and the size should be shrinked by 1.0 unit. @@ -307,10 +306,10 @@ - (void)drawRow:(int)row { } else { // Draw normal background for (int i = COLUMN_SHORTCUT; i < NUMBER_OF_COLUMNS; ++i) { - mozc::Rect cellRect = tableLayout_->GetCellRect(row, i); + const mozc::Rect cellRect = tableLayout_.GetCellRect(row, i); if (cellRect.size.width > 0 && cellRect.size.height > 0 && - style_->text_styles(i).has_background_color()) { - [MacViewUtil::ToNSColor(style_->text_styles(i).background_color()) set]; + style_.text_styles(i).has_background_color()) { + [MacViewUtil::ToNSColor(style_.text_styles(i).background_color()) set]; [NSBezierPath fillRect:MacViewUtil::ToNSRect(cellRect)]; } } @@ -318,18 +317,18 @@ - (void)drawRow:(int)row { NSArray *candidate = [candidateStringsCache_ objectAtIndex:row]; for (int i = COLUMN_SHORTCUT; i < NUMBER_OF_COLUMNS; ++i) { - NSAttributedString *text = [candidate objectAtIndex:i]; - NSRect cellRect = MacViewUtil::ToNSRect(tableLayout_->GetCellRect(row, i)); + const NSAttributedString *text = [candidate objectAtIndex:i]; + NSRect cellRect = MacViewUtil::ToNSRect(tableLayout_.GetCellRect(row, i)); NSPoint &candidatePosition = cellRect.origin; // Adjust the positions - candidatePosition.x += style_->text_styles(i).left_padding(); + candidatePosition.x += style_.text_styles(i).left_padding(); candidatePosition.y += (cellRect.size.height - [text size].height) / 2; [text drawAtPoint:candidatePosition]; } if (candidates_.candidate(row).has_information_id()) { - NSRect rect = MacViewUtil::ToNSRect(tableLayout_->GetRowRect(row)); - [MacViewUtil::ToNSColor(style_->focused_border_color()) set]; + NSRect rect = MacViewUtil::ToNSRect(tableLayout_.GetRowRect(row)); + [MacViewUtil::ToNSColor(style_.focused_border_color()) set]; rect.origin.x += rect.size.width - 6.0; rect.size.width = 4.0; rect.origin.y += 2.0; @@ -339,103 +338,108 @@ - (void)drawRow:(int)row { } - (void)drawFooter { - if (candidates_.has_footer()) { - const mozc::commands::Footer &footer = candidates_.footer(); - NSRect footerRect = MacViewUtil::ToNSRect(tableLayout_->GetFooterRect()); - - // Draw footer border - for (int i = 0; i < style_->footer_border_colors_size(); ++i) { - [MacViewUtil::ToNSColor(style_->footer_border_colors(i)) set]; - NSPoint fromPoint = NSMakePoint(footerRect.origin.x, footerRect.origin.y + 0.5); - NSPoint toPoint = - NSMakePoint(footerRect.origin.x + footerRect.size.width, footerRect.origin.y + 0.5); - [NSBezierPath strokeLineFromPoint:fromPoint toPoint:toPoint]; - footerRect.origin.y += 1; - } + if (!candidates_.has_footer()) { + return; + } + const mozc::commands::Footer &footer = candidates_.footer(); + NSRect footerRect = MacViewUtil::ToNSRect(tableLayout_.GetFooterRect()); + + // Draw footer border + for (int i = 0; i < style_.footer_border_colors_size(); ++i) { + [MacViewUtil::ToNSColor(style_.footer_border_colors(i)) set]; + const NSPoint fromPoint = NSMakePoint(footerRect.origin.x, footerRect.origin.y + 0.5); + const NSPoint toPoint = + NSMakePoint(footerRect.origin.x + footerRect.size.width, footerRect.origin.y + 0.5); + [NSBezierPath strokeLineFromPoint:fromPoint toPoint:toPoint]; + footerRect.origin.y += 1; + } - // Draw Footer background and data if necessary - NSGradient *footerBackground = [[NSGradient alloc] - initWithStartingColor:MacViewUtil::ToNSColor(style_->footer_top_color()) - endingColor:MacViewUtil::ToNSColor(style_->footer_bottom_color())]; - [footerBackground drawInRect:footerRect angle:90.0]; - - // Draw logo - if (footer.logo_visible() && g_LogoImage) { - [g_LogoImage drawAtPoint:footerRect.origin - fromRect:NSZeroRect /* means draw entire image */ - operation:NSCompositingOperationSourceOver - fraction:1.0 /* opacity */]; - NSSize logoSize = [g_LogoImage size]; - footerRect.origin.x += logoSize.width; - footerRect.size.width -= logoSize.width; - } + // Draw Footer background and data if necessary + const NSGradient *footerBackground = [[NSGradient alloc] + initWithStartingColor:MacViewUtil::ToNSColor(style_.footer_top_color()) + endingColor:MacViewUtil::ToNSColor(style_.footer_bottom_color())]; + [footerBackground drawInRect:footerRect angle:90.0]; + + // Draw logo + if (footer.logo_visible() && logoImage_) { + const NSPoint logoPoint = footerRect.origin; + const NSSize logoSize = logoImage_.size; + const NSRect logoRect = NSMakeRect(logoPoint.x, logoPoint.y, logoSize.width, logoSize.height); + [logoImage_ drawInRect:logoRect + fromRect:NSZeroRect // Draw the entire image + operation:NSCompositingOperationSourceOver + fraction:1.0 // Opacity + respectFlipped:YES + hints:nil]; + footerRect.origin.x += logoSize.width; + footerRect.size.width -= logoSize.width; + } - // Draw label - if (footer.has_label()) { - NSAttributedString *footerLabel = - MacViewUtil::ToNSAttributedString(footer.label(), style_->footer_style()); - footerRect.origin.x += style_->footer_style().left_padding(); - NSSize labelSize = [footerLabel size]; - NSPoint labelPosition = footerRect.origin; - labelPosition.y += (footerRect.size.height - labelSize.height) / 2; - [footerLabel drawAtPoint:labelPosition]; - } + // Draw label + if (footer.has_label()) { + const NSAttributedString *footerLabel = + MacViewUtil::ToNSAttributedString(footer.label(), style_.footer_style()); + footerRect.origin.x += style_.footer_style().left_padding(); + const NSSize labelSize = [footerLabel size]; + NSPoint labelPosition = footerRect.origin; + labelPosition.y += (footerRect.size.height - labelSize.height) / 2; + [footerLabel drawAtPoint:labelPosition]; + } - // Draw sub_label - if (footer.has_sub_label()) { - NSAttributedString *footerSubLabel = - MacViewUtil::ToNSAttributedString(footer.sub_label(), style_->footer_sub_label_style()); - footerRect.origin.x += style_->footer_sub_label_style().left_padding(); - NSSize subLabelSize = [footerSubLabel size]; - NSPoint subLabelPosition = footerRect.origin; - subLabelPosition.y += (footerRect.size.height - subLabelSize.height) / 2; - [footerSubLabel drawAtPoint:subLabelPosition]; - } + // Draw sub_label + if (footer.has_sub_label()) { + const NSAttributedString *footerSubLabel = + MacViewUtil::ToNSAttributedString(footer.sub_label(), style_.footer_sub_label_style()); + footerRect.origin.x += style_.footer_sub_label_style().left_padding(); + const NSSize subLabelSize = [footerSubLabel size]; + NSPoint subLabelPosition = footerRect.origin; + subLabelPosition.y += (footerRect.size.height - subLabelSize.height) / 2; + [footerSubLabel drawAtPoint:subLabelPosition]; + } - // Draw footer index (e.g. "10/120") - if (footer.index_visible()) { - int focusedIndex = candidates_.focused_index(); - int totalItems = candidates_.size(); - NSString *footerIndex = [NSString stringWithFormat:@"%d/%d", focusedIndex + 1, totalItems]; - NSAttributedString *footerAttributedIndex = - MacViewUtil::ToNSAttributedString([footerIndex UTF8String], style_ -> footer_style()); - NSSize footerSize = [footerAttributedIndex size]; - NSPoint footerPosition = footerRect.origin; - footerPosition.x = footerPosition.x + footerRect.size.width - footerSize.width - - style_->footer_style().right_padding(); - [footerAttributedIndex drawAtPoint:footerPosition]; - } + // Draw footer index (e.g. "10/120") + if (footer.index_visible()) { + const std::string footerIndex = + absl::StrFormat("%d/%d", + candidates_.focused_index() + 1, // +1 to 1-origin from 0-origin. + candidates_.size()); + const NSAttributedString *footerAttributedIndex = + MacViewUtil::ToNSAttributedString(footerIndex, style_.footer_style()); + const NSSize footerSize = [footerAttributedIndex size]; + NSPoint footerPosition = footerRect.origin; + footerPosition.x = footerPosition.x + footerRect.size.width - footerSize.width - + style_.footer_style().right_padding(); + [footerAttributedIndex drawAtPoint:footerPosition]; } } - (void)drawVScrollBar { - const mozc::Rect &vscrollRect = tableLayout_->GetVScrollBarRect(); + const mozc::Rect vscrollRect = tableLayout_.GetVScrollBarRect(); + if (vscrollRect.IsRectEmpty() || candidates_.candidate_size() <= 0) { + return; + } - if (!vscrollRect.IsRectEmpty() && candidates_.candidate_size() > 0) { - const int beginIndex = candidates_.candidate(0).index(); - const int candidatesTotal = candidates_.size(); - const int endIndex = candidates_.candidate(candidates_.candidate_size() - 1).index(); + const int beginIndex = candidates_.candidate(0).index(); + const int candidatesTotal = candidates_.size(); + const int endIndex = candidates_.candidate(candidates_.candidate_size() - 1).index(); - [MacViewUtil::ToNSColor(style_->scrollbar_background_color()) set]; - [NSBezierPath fillRect:MacViewUtil::ToNSRect(vscrollRect)]; + [MacViewUtil::ToNSColor(style_.scrollbar_background_color()) set]; + [NSBezierPath fillRect:MacViewUtil::ToNSRect(vscrollRect)]; - const mozc::Rect &indicatorRect = - tableLayout_->GetVScrollIndicatorRect(beginIndex, endIndex, candidatesTotal); - [MacViewUtil::ToNSColor(style_->scrollbar_indicator_color()) set]; - [NSBezierPath fillRect:MacViewUtil::ToNSRect(indicatorRect)]; - } + const mozc::Rect indicatorRect = + tableLayout_.GetVScrollIndicatorRect(beginIndex, endIndex, candidatesTotal); + [MacViewUtil::ToNSColor(style_.scrollbar_indicator_color()) set]; + [NSBezierPath fillRect:MacViewUtil::ToNSRect(indicatorRect)]; } #pragma mark event handling callbacks -const char *Inspect(id obj) { return [[NSString stringWithFormat:@"%@", obj] UTF8String]; } - - (void)mouseDown:(NSEvent *)event { - mozc::Point localPos = MacViewUtil::ToPoint([self convertPoint:[event locationInWindow] - fromView:nil]); + const mozc::Point localPos = MacViewUtil::ToPoint([self convertPoint:[event locationInWindow] + fromView:nil]); int clickedRow = -1; - for (int i = 0; i < tableLayout_->number_of_rows(); ++i) { - mozc::Rect rowRect = tableLayout_->GetRowRect(i); + for (int i = 0; i < tableLayout_.number_of_rows(); ++i) { + const mozc::Rect rowRect = tableLayout_.GetRowRect(i); if (rowRect.PtrInRect(localPos)) { clickedRow = i; break; @@ -449,16 +453,16 @@ - (void)mouseDown:(NSEvent *)event { } - (void)mouseUp:(NSEvent *)event { - mozc::Point localPos = MacViewUtil::ToPoint([self convertPoint:[event locationInWindow] - fromView:nil]); + const mozc::Point localPos = MacViewUtil::ToPoint([self convertPoint:[event locationInWindow] + fromView:nil]); if (command_sender_ == nullptr) { return; } - if (candidates_.candidate_size() < tableLayout_->number_of_rows()) { + if (candidates_.candidate_size() < tableLayout_.number_of_rows()) { return; } - for (int i = 0; i < tableLayout_->number_of_rows(); ++i) { - mozc::Rect rowRect = tableLayout_->GetRowRect(i); + for (int i = 0; i < tableLayout_.number_of_rows(); ++i) { + const mozc::Rect rowRect = tableLayout_.GetRowRect(i); if (rowRect.PtrInRect(localPos)) { SessionCommand command; command.set_type(SessionCommand::SELECT_CANDIDATE); diff --git a/src/rewriter/BUILD.bazel b/src/rewriter/BUILD.bazel index 01b20fc89..30a13c390 100644 --- a/src/rewriter/BUILD.bazel +++ b/src/rewriter/BUILD.bazel @@ -1584,6 +1584,8 @@ mozc_cc_library( "//base/container:serialized_string_array", "//converter:segments", "//dictionary:pos_matcher", + "@com_google_absl//absl/base:no_destructor", + "@com_google_absl//absl/container:flat_hash_set", "@com_google_absl//absl/strings", ], ) diff --git a/src/rewriter/number_compound_util.cc b/src/rewriter/number_compound_util.cc index 7f33c5343..62dab3c66 100644 --- a/src/rewriter/number_compound_util.cc +++ b/src/rewriter/number_compound_util.cc @@ -31,7 +31,10 @@ #include #include +#include +#include "absl/base/no_destructor.h" +#include "absl/container/flat_hash_set.h" #include "absl/strings/string_view.h" #include "base/container/serialized_string_array.h" #include "base/util.h" @@ -112,7 +115,7 @@ bool IsNumber(const SerializedStringArray &suffix_array, // check the opportunities of rewriting such number nouns. // TODO(toshiyuki, team): It may be better to set number POS to such number // noun entries at dictionary build time. correct POS. Then, we can omit the - // following runtime strcture check. + // following runtime structure check. if (!pos_matcher.IsGeneralNoun(cand.lid)) { return false; } @@ -126,6 +129,25 @@ bool IsNumber(const SerializedStringArray &suffix_array, suffix_array, cand.content_value, &number, &suffix, &script_type)) { return false; } + // Some number including general noun should be excluded from IsNumber(). + static const absl::NoDestructor< + absl::flat_hash_set>> + kExceptions({ + {"いっこう", "一行"}, + {"さんしゃ", "三者"}, + {"さんきゃく", "三脚"}, + {"しきゅう", "四球"}, + {"しき", "四季"}, + {"ろっぽう", "六法"}, + {"ろっぽう", "六方"}, + {"ろっかい", "六界"}, + {"ろくどう", "六道"}, + {"しちりん", "七輪"}, + {"やえ", "八重"}, + }); + if (kExceptions->contains(std::make_pair(cand.key, cand.value))) { + return false; + } return !number.empty(); } diff --git a/src/rewriter/number_compound_util_test.cc b/src/rewriter/number_compound_util_test.cc index 1dfb2464c..4cfd82fe1 100644 --- a/src/rewriter/number_compound_util_test.cc +++ b/src/rewriter/number_compound_util_test.cc @@ -136,7 +136,9 @@ TEST(NumberCompoundUtilTest, IsNumber) { std::unique_ptr buf; const absl::string_view data = SerializedStringArray::SerializeToBuffer( { + // Should be sorted "回", + "行", "階", }, &buf); @@ -178,6 +180,25 @@ TEST(NumberCompoundUtilTest, IsNumber) { c.lid = pos_matcher.GetAdverbId(); c.rid = pos_matcher.GetAdverbId(); EXPECT_FALSE(IsNumber(suffix_array, pos_matcher, c)); + + c = Segment::Candidate(); + c.key = "いちぎょう"; + c.content_key = "いちぎょう"; + c.value = "一行"; + c.content_value = "一行"; + c.lid = pos_matcher.GetGeneralNounId(); + c.rid = pos_matcher.GetGeneralNounId(); + EXPECT_TRUE(IsNumber(suffix_array, pos_matcher, c)); + + // Exception entry + c = Segment::Candidate(); + c.key = "いっこう"; + c.content_key = "いっこう"; + c.value = "一行"; + c.content_value = "一行"; + c.lid = pos_matcher.GetGeneralNounId(); + c.rid = pos_matcher.GetGeneralNounId(); + EXPECT_FALSE(IsNumber(suffix_array, pos_matcher, c)); } } // namespace number_compound_util