Codebase list mozc / 9fbcdd5
Stop working around a limitation in GTK IM-module. There is a long standing limitation in GTK IM-module that IMEs cannot retrieve the screen coordinates of each composing character, which is definitely needed to align suggestion window to the left edge of composing text. In ibus-mozc, we have worked around this limitation by recording the cursor rectangle in the mozc server rather and simulating the character screen coordinates from those cursor rectangles since OSS Mozc 1.3.911.102 (a1fae21a95ffe8955a69c6c93255938db1f22e46). This emulation has, however, never been perfect. Following issues are actually edge cases of the emulation. - #243: ibus predict window is shown at the previous cursor position - https://bugzilla.mozilla.org/show_bug.cgi?id=1120851 Therefore we decided to remove the above emulation from ibus-mozc and live in more robust but unsophisticated world instead. With this CL, the suggestion window will show up just under the cursor location rather than being aligned with composing text. This clean-up also enables us to refactor mozc-server without bothering future ibus-mozc maintainers because that emulation code that is implemented in mozc-server. In subsequent CLs we can remove the emulation code without breaking existing ibus-mozc client. Closes #243. BUG=#243 TEST=manually done on Ubuntu 14.04. Yohei Yukawa 8 years ago
11 changed file(s) with 164 addition(s) and 83 deletion(s). Raw diff Collapse all Expand all
00 MAJOR=2
11 MINOR=17
2 BUILD=2108
2 BUILD=2109
33 REVISION=102
44 # NACL_DICTIONARY_VERSION is the target version of the system dictionary to be
55 # downloaded by NaCl Mozc.
100100 const Size new_window_size = candidate_window_->Update(candidates);
101101
102102 Point new_window_pos = candidate_window_->GetWindowPos();
103 if (candidates.has_window_location()) {
104 if (candidates.window_location() == commands::Candidates::CARET) {
105 DCHECK(candidates.has_caret_rectangle());
106 new_window_pos.x = candidates.caret_rectangle().x();
107 new_window_pos.y = candidates.caret_rectangle().y()
108 + candidates.caret_rectangle().height();
109 } else {
110 DCHECK(candidates.has_composition_rectangle());
111 new_window_pos.x = candidates.composition_rectangle().x();
112 new_window_pos.y = candidates.composition_rectangle().y()
113 + candidates.composition_rectangle().height();
114 }
103 if (command.has_preedit_rectangle()) {
104 new_window_pos.x = command.preedit_rectangle().left();
105 new_window_pos.y = command.preedit_rectangle().bottom();
115106 }
116107
117108 const Rect working_area = GetMonitorRect(new_window_pos.x, new_window_pos.y);
118109 const Point alignment_base_point_in_local_window_coord(
119110 candidate_window_->GetCandidateColumnInClientCord().Left(), 0);
120 const Rect caret_rect(candidates.caret_rectangle().x(),
121 candidates.caret_rectangle().y(),
122 candidates.caret_rectangle().width(),
123 candidates.caret_rectangle().height());
111 const auto &preedit_rect = command.preedit_rectangle();
112 const Rect caret_rect(preedit_rect.left(),
113 preedit_rect.top(),
114 preedit_rect.right() - preedit_rect.left(),
115 preedit_rect.bottom() - preedit_rect.top());
124116 // |caret_rect| is not always equal to preedit rect but can be an alternative
125117 // in terms of positional calculation, especially for vertical adjustment in
126118 // horizontal writing.
330330 const Size window_size(35, 45);
331331 const gint monitor = 0x7777;
332332
333 candidates->set_window_location(commands::Candidates::CARET);
334333 const Rect caret_rect(16, 26, 2, 13);
335 commands::Rectangle *rectangle = candidates->mutable_caret_rectangle();
336 rectangle->set_x(caret_rect.Left());
337 rectangle->set_y(caret_rect.Top());
338 rectangle->set_width(caret_rect.Width());
339 rectangle->set_height(caret_rect.Height());
334 auto *rectangle = command.mutable_preedit_rectangle();
335 rectangle->set_left(caret_rect.Left());
336 rectangle->set_top(caret_rect.Top());
337 rectangle->set_right(caret_rect.Right());
338 rectangle->set_bottom(caret_rect.Bottom());
340339 const Point expected_window_position(
341340 caret_rect.Left() - client_cord_rect.Left(),
342341 caret_rect.Top() + caret_rect.Height());
399398 const Size window_size(35, 45);
400399 const gint monitor = 0x7777;
401400
402 candidates->set_window_location(commands::Candidates::COMPOSITION);
403401 const Rect comp_rect(16, 26, 2, 13);
404 commands::Rectangle *rectangle
405 = candidates->mutable_composition_rectangle();
406 rectangle->set_x(comp_rect.Left());
407 rectangle->set_y(comp_rect.Top());
408 rectangle->set_width(comp_rect.Width());
409 rectangle->set_height(comp_rect.Height());
402 auto *rectangle = command.mutable_preedit_rectangle();
403 rectangle->set_left(comp_rect.Left());
404 rectangle->set_top(comp_rect.Top());
405 rectangle->set_right(comp_rect.Right());
406 rectangle->set_bottom(comp_rect.Bottom());
410407 const Point expected_window_position(
411408 comp_rect.Left() - client_cord_rect.Left(),
412409 comp_rect.Top() + comp_rect.Height());
4848 virtual void Update(IBusEngine *engine,
4949 const commands::Output &output) = 0;
5050
51 // Updates candidate state. This function also shows or hides candidate window
52 // based on the last |Update| call.
53 virtual void UpdateCursorRect(IBusEngine *engine) = 0;
54
5155 // Hides candidate window.
5256 virtual void Hide(IBusEngine *engine) = 0;
5357
5353 }
5454
5555 bool GtkCandidateWindowHandler::SendUpdateCommand(
56 const commands::Output &output, bool visibility) const {
56 IBusEngine *engine,
57 const commands::Output &output,
58 bool visibility) const {
5759 using commands::RendererCommand;
60 RendererCommand command;
5861
59 RendererCommand command;
60 command.mutable_output()->CopyFrom(output);
61 command.set_type(commands::RendererCommand::UPDATE);
62 *command.mutable_output() = output;
63 command.set_type(RendererCommand::UPDATE);
6264 command.set_visible(visibility);
6365 RendererCommand::ApplicationInfo *appinfo
6466 = command.mutable_application_info();
67
68 auto *preedit_rectangle = command.mutable_preedit_rectangle();
69 const auto &cursor_area = engine->cursor_area;
70 preedit_rectangle->set_left(cursor_area.x);
71 preedit_rectangle->set_top(cursor_area.y);
72 preedit_rectangle->set_right(cursor_area.x + cursor_area.width);
73 preedit_rectangle->set_bottom(cursor_area.y + cursor_area.height);
6574
6675 // Set pid
6776 static_assert(sizeof(::getpid()) <= sizeof(appinfo->process_id()),
8291
8392 void GtkCandidateWindowHandler::Update(IBusEngine *engine,
8493 const commands::Output &output) {
85 last_update_output_->CopyFrom(output);
94 *last_update_output_ = output;
8695
87 SendUpdateCommand(output, output.candidates().candidate_size() != 0);
96 UpdateCursorRect(engine);
97 }
98
99 void GtkCandidateWindowHandler::UpdateCursorRect(IBusEngine *engine) {
100 const bool has_candidates =
101 last_update_output_->has_candidates() &&
102 last_update_output_->candidates().candidate_size() > 0;
103 SendUpdateCommand(engine, *last_update_output_, has_candidates);
88104 }
89105
90106 void GtkCandidateWindowHandler::Hide(IBusEngine *engine) {
91 SendUpdateCommand(*(last_update_output_.get()), false);
107 SendUpdateCommand(engine, *last_update_output_, false);
92108 }
93109
94110 void GtkCandidateWindowHandler::Show(IBusEngine *engine) {
95 SendUpdateCommand(*(last_update_output_.get()), true);
111 SendUpdateCommand(engine, *last_update_output_, true);
96112 }
97113
98114 void GtkCandidateWindowHandler::OnIBusCustomFontDescriptionChanged(
3535 #include "unix/ibus/candidate_window_handler_interface.h"
3636
3737 namespace mozc {
38 namespace commands {
39 class RendererCommand;
40 } // namespace commands
3841 namespace renderer {
3942 class RendererInterface;
4043 } // namespace renderer
4750 virtual ~GtkCandidateWindowHandler();
4851
4952 virtual void Update(IBusEngine *engine, const commands::Output &output);
53 virtual void UpdateCursorRect(IBusEngine *engine);
5054 virtual void Hide(IBusEngine *engine);
5155 virtual void Show(IBusEngine *engine);
5256
5761 bool use_custom_font_description);
5862
5963 protected:
60 bool SendUpdateCommand(const commands::Output &output, bool visibility) const;
64 bool SendUpdateCommand(IBusEngine *engine,
65 const commands::Output &output,
66 bool visibility) const;
6167
6268 std::unique_ptr<renderer::RendererInterface> renderer_;
6369 std::unique_ptr<commands::Output> last_update_output_;
3030
3131 #include <unistd.h> // for getpid()
3232
33 #include "base/coordinates.h"
3334 #include "protocol/renderer_command.pb.h"
3435 #include "renderer/renderer_mock.h"
3536 #include "testing/base/public/gmock.h"
109110 return true;
110111 }
111112
113 MATCHER_P(PreeditRectangleEq, rect, "") {
114 if (!arg.has_preedit_rectangle()) {
115 *result_listener
116 << "RendererCommand::preedit_rectangle does not exist";
117 return false;
118 }
119 const auto &actual_rect = arg.preedit_rectangle();
120 if (rect.Left() != actual_rect.left()) {
121 *result_listener << "left field does not match\n"
122 << " expected: " << rect.Left() << "\n"
123 << " actual: " << actual_rect.left();
124 return false;
125 }
126 if (rect.Top() != actual_rect.top()) {
127 *result_listener << "top field does not match\n"
128 << " expected: " << rect.Top() << "\n"
129 << " actual: " << actual_rect.top();
130 return false;
131 }
132 if (rect.Right() != actual_rect.right()) {
133 *result_listener << "right field does not match\n"
134 << " expected: " << rect.Right() << "\n"
135 << " actual: " << actual_rect.right();
136 return false;
137 }
138 if (rect.Bottom() != actual_rect.bottom()) {
139 *result_listener << "bottom field does not match\n"
140 << " expected: " << rect.Bottom() << "\n"
141 << " actual: " << actual_rect.bottom();
142 return false;
143 }
144 return true;
145 }
146
112147 MATCHER_P(OutputEq, expected, "") {
113148 if (expected.Utf8DebugString() != arg.Utf8DebugString()) {
114149 *result_listener
128163 } // namespace
129164
130165 TEST(GtkCandidateWindowHandlerTest, SendUpdateCommandTest) {
166 const Rect kExpectedCursorArea(10, 20, 200, 100);
167
168 IBusEngine engine = {};
169 engine.cursor_area.x = kExpectedCursorArea.Left();
170 engine.cursor_area.y = kExpectedCursorArea.Top();
171 engine.cursor_area.width = kExpectedCursorArea.Width();
172 engine.cursor_area.height = kExpectedCursorArea.Height();
173
131174 {
132175 SCOPED_TRACE("visibility check. false case");
133176 Output output;
134177 RendererMock *renderer_mock = new RendererMock();
135178 TestableGtkCandidateWindowHandler gtk_candidate_window_handler(
136179 renderer_mock);
137 EXPECT_CALL_EXEC_COMMAND(*renderer_mock, VisibilityEq(false));
138 gtk_candidate_window_handler.SendUpdateCommand(output, false);
180 EXPECT_CALL_EXEC_COMMAND(*renderer_mock,
181 VisibilityEq(false),
182 PreeditRectangleEq(kExpectedCursorArea));
183 gtk_candidate_window_handler.SendUpdateCommand(&engine, output, false);
139184 }
140185 {
141186 SCOPED_TRACE("visibility check. true case");
143188 RendererMock *renderer_mock = new RendererMock();
144189 TestableGtkCandidateWindowHandler gtk_candidate_window_handler(
145190 renderer_mock);
146 EXPECT_CALL_EXEC_COMMAND(*renderer_mock, VisibilityEq(true));
147 gtk_candidate_window_handler.SendUpdateCommand(output, true);
191 EXPECT_CALL_EXEC_COMMAND(*renderer_mock,
192 VisibilityEq(true),
193 PreeditRectangleEq(kExpectedCursorArea));
194 gtk_candidate_window_handler.SendUpdateCommand(&engine, output, true);
148195 }
149196 {
150197 SCOPED_TRACE("return value check. false case.");
152199 RendererMock *renderer_mock = new RendererMock();
153200 TestableGtkCandidateWindowHandler gtk_candidate_window_handler(
154201 renderer_mock);
155 EXPECT_CALL_EXEC_COMMAND(*renderer_mock, VisibilityEq(true))
202 EXPECT_CALL_EXEC_COMMAND(*renderer_mock,
203 VisibilityEq(true),
204 PreeditRectangleEq(kExpectedCursorArea))
156205 .WillOnce(Return(false));
157 EXPECT_FALSE(gtk_candidate_window_handler.SendUpdateCommand(output, true));
206 EXPECT_FALSE(gtk_candidate_window_handler.SendUpdateCommand(
207 &engine, output, true));
158208 }
159209 {
160210 SCOPED_TRACE("return value check. true case.");
162212 RendererMock *renderer_mock = new RendererMock();
163213 TestableGtkCandidateWindowHandler gtk_candidate_window_handler(
164214 renderer_mock);
165 EXPECT_CALL_EXEC_COMMAND(*renderer_mock, VisibilityEq(true))
215 EXPECT_CALL_EXEC_COMMAND(*renderer_mock,
216 VisibilityEq(true),
217 PreeditRectangleEq(kExpectedCursorArea))
166218 .WillOnce(Return(true));
167 EXPECT_TRUE(gtk_candidate_window_handler.SendUpdateCommand(output, true));
219 EXPECT_TRUE(gtk_candidate_window_handler.SendUpdateCommand(
220 &engine, output, true));
168221 }
169222 }
170223
171224 TEST(GtkCandidateWindowHandlerTest, UpdateTest) {
225 const Rect kExpectedCursorArea(10, 20, 200, 100);
226
227 IBusEngine engine = {};
228 engine.cursor_area.x = kExpectedCursorArea.Left();
229 engine.cursor_area.y = kExpectedCursorArea.Top();
230 engine.cursor_area.width = kExpectedCursorArea.Width();
231 engine.cursor_area.height = kExpectedCursorArea.Height();
232
172233 const int sample_idx1 = 0;
173234 const int sample_idx2 = 1;
174235 const char *sample_candidate1 = "SAMPLE_CANDIDATE1";
179240 RendererMock *renderer_mock = new RendererMock();
180241 TestableGtkCandidateWindowHandler gtk_candidate_window_handler(
181242 renderer_mock);
182 EXPECT_CALL_EXEC_COMMAND(*renderer_mock, VisibilityEq(false));
183 gtk_candidate_window_handler.Update(NULL, output);
243 EXPECT_CALL_EXEC_COMMAND(*renderer_mock,
244 VisibilityEq(false),
245 PreeditRectangleEq(kExpectedCursorArea));
246 gtk_candidate_window_handler.Update(&engine, output);
184247 }
185248 {
186249 SCOPED_TRACE("If there is at least one candidate, "
193256 RendererMock *renderer_mock = new RendererMock();
194257 TestableGtkCandidateWindowHandler gtk_candidate_window_handler(
195258 renderer_mock);
196 EXPECT_CALL_EXEC_COMMAND(*renderer_mock, VisibilityEq(true));
197 gtk_candidate_window_handler.Update(NULL, output);
259 EXPECT_CALL_EXEC_COMMAND(*renderer_mock,
260 VisibilityEq(true),
261 PreeditRectangleEq(kExpectedCursorArea));
262 gtk_candidate_window_handler.Update(&engine, output);
198263 }
199264 {
200265 SCOPED_TRACE("Update last updated output protobuf object.");
220285 Property(&RendererCommand::output,
221286 OutputEq(output2)))
222287 .WillOnce(Return(true));
223 gtk_candidate_window_handler.Update(NULL, output1);
288 gtk_candidate_window_handler.Update(&engine, output1);
224289 EXPECT_THAT(*(gtk_candidate_window_handler.last_update_output_.get()),
225290 OutputEq(output1));
226 gtk_candidate_window_handler.Update(NULL, output2);
291 gtk_candidate_window_handler.Update(&engine, output2);
227292 EXPECT_THAT(*(gtk_candidate_window_handler.last_update_output_.get()),
228293 OutputEq(output2));
229294 }
230295 }
231296
232297 TEST(GtkCandidateWindowHandlerTest, HideTest) {
298 const Rect kExpectedCursorArea(10, 20, 200, 100);
299
300 IBusEngine engine = {};
301 engine.cursor_area.x = kExpectedCursorArea.Left();
302 engine.cursor_area.y = kExpectedCursorArea.Top();
303 engine.cursor_area.width = kExpectedCursorArea.Width();
304 engine.cursor_area.height = kExpectedCursorArea.Height();
305
233306 RendererMock *renderer_mock = new RendererMock();
234307 TestableGtkCandidateWindowHandler gtk_candidate_window_handler(
235308 renderer_mock);
236 EXPECT_CALL_EXEC_COMMAND(*renderer_mock, VisibilityEq(false));
237 gtk_candidate_window_handler.Hide(NULL);
309 EXPECT_CALL_EXEC_COMMAND(*renderer_mock,
310 VisibilityEq(false),
311 PreeditRectangleEq(kExpectedCursorArea));
312 gtk_candidate_window_handler.Hide(&engine);
238313 }
239314
240315 TEST(GtkCandidateWindowHandlerTest, ShowTest) {
316 const Rect kExpectedCursorArea(10, 20, 200, 100);
317
318 IBusEngine engine = {};
319 engine.cursor_area.x = kExpectedCursorArea.Left();
320 engine.cursor_area.y = kExpectedCursorArea.Top();
321 engine.cursor_area.width = kExpectedCursorArea.Width();
322 engine.cursor_area.height = kExpectedCursorArea.Height();
323
241324 RendererMock *renderer_mock = new RendererMock();
242325 TestableGtkCandidateWindowHandler gtk_candidate_window_handler(
243326 renderer_mock);
244 EXPECT_CALL_EXEC_COMMAND(*renderer_mock, VisibilityEq(true));
245 gtk_candidate_window_handler.Show(NULL);
327 EXPECT_CALL_EXEC_COMMAND(*renderer_mock,
328 VisibilityEq(true),
329 PreeditRectangleEq(kExpectedCursorArea));
330 gtk_candidate_window_handler.Show(&engine);
246331 }
247332
248333 } // namespace ibus
9191 UpdateAuxiliaryText(engine, output);
9292 }
9393
94 void IBusCandidateWindowHandler::UpdateCursorRect(IBusEngine *engine) {
95 // Nothing to do because IBus takes care of where to show its candidate
96 // window.
97 }
98
9499 void IBusCandidateWindowHandler::Hide(IBusEngine *engine) {
95100 ibus_engine_hide_lookup_table(engine);
96101 ibus_engine_hide_auxiliary_text(engine);
4040 virtual ~IBusCandidateWindowHandler();
4141
4242 virtual void Update(IBusEngine *engine, const commands::Output &output);
43 virtual void UpdateCursorRect(IBusEngine *engine);
4344 virtual void Hide(IBusEngine *engine);
4445 virtual void Show(IBusEngine *engine);
4546
363363 return FALSE;
364364 }
365365
366 // Send current caret location to mozc_server to manage suggest window
367 // position.
368 // TODO(nona): Merge SendKey event to reduce IPC cost.
369 // TODO(nona): Add a unit test against b/6209562.
370 SendCaretLocation(engine->cursor_area.x,
371 engine->cursor_area.y,
372 engine->cursor_area.width,
373 engine->cursor_area.height);
374
375366 // TODO(yusukes): use |layout| in IBusEngineDesc if possible.
376367 const bool layout_is_jp =
377368 !g_strcmp0(ibus_engine_get_name(engine), "mozc-jp");
447438 gint y,
448439 gint w,
449440 gint h) {
450 // Do nothing
441 GetCandidateWindowHandler(engine)->UpdateCursorRect(engine);
451442 }
452443
453444 void MozcEngine::SetContentType(IBusEngine *engine,
789780 #endif // not ENABLE_GTK_RENDERER
790781 }
791782
792 void MozcEngine::SendCaretLocation(uint32 x, uint32 y, uint32 width,
793 uint32 height) {
794 commands::Output output;
795 commands::SessionCommand command;
796 command.set_type(commands::SessionCommand::SEND_CARET_LOCATION);
797 commands::Rectangle *caret_rectangle = command.mutable_caret_rectangle();
798 caret_rectangle->set_x(x);
799 caret_rectangle->set_y(y);
800 caret_rectangle->set_width(width);
801 caret_rectangle->set_height(height);
802 client_->SendCommand(command, &output);
803 }
804
805783 } // namespace ibus
806784 } // namespace mozc
154154 // message, then hides a preedit string and the candidate window.
155155 void RevertSession(IBusEngine *engine);
156156
157 // Sends current caret location to mozc_server.
158 void SendCaretLocation(uint32 x, uint32 y, uint32 width, uint32 height);
159
160157 CandidateWindowHandlerInterface *GetCandidateWindowHandler(
161158 IBusEngine *engine);
162159