1 // Copyright 2010-2018, Google Inc.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
6 // met:
7 //
8 //     * Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 //     * Redistributions in binary form must reproduce the above
11 // copyright notice, this list of conditions and the following disclaimer
12 // in the documentation and/or other materials provided with the
13 // distribution.
14 //     * Neither the name of Google Inc. nor the names of its
15 // contributors may be used to endorse or promote products derived from
16 // this software without specific prior written permission.
17 //
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 
30 #include "renderer/unix/window_manager.h"
31 
32 #include "base/logging.h"
33 #include "protocol/renderer_command.pb.h"
34 #include "renderer/unix/gtk_wrapper.h"
35 #include "renderer/window_util.h"
36 
37 namespace mozc {
38 namespace renderer {
39 namespace gtk {
40 
WindowManager(GtkWindowInterface * candidate_window,GtkWindowInterface * infolist_window,GtkWrapperInterface * gtk)41 WindowManager::WindowManager(GtkWindowInterface *candidate_window,
42                              GtkWindowInterface *infolist_window,
43                              GtkWrapperInterface *gtk)
44     : candidate_window_(candidate_window),
45       infolist_window_(infolist_window),
46       gtk_(gtk) {
47 }
48 
~WindowManager()49 WindowManager::~WindowManager() {
50 }
51 
Initialize()52 void WindowManager::Initialize() {
53   // Should call ShowWindow function in all window, otherwise each Initialize
54   // function will fail.
55   ShowAllWindows();
56   HideAllWindows();
57   candidate_window_->Initialize();
58   infolist_window_->Initialize();
59 }
60 
HideAllWindows()61 void WindowManager::HideAllWindows() {
62   candidate_window_->HideWindow();
63   infolist_window_->HideWindow();
64 }
65 
ShowAllWindows()66 void WindowManager::ShowAllWindows() {
67   candidate_window_->ShowWindow();
68   infolist_window_->ShowWindow();
69 }
70 
71 // static
ShouldShowCandidateWindow(const commands::RendererCommand & command)72 bool WindowManager::ShouldShowCandidateWindow(
73     const commands::RendererCommand &command) {
74   if (!command.visible()) {
75     return false;
76   }
77 
78   DCHECK(command.has_output());
79   const commands::Output &output = command.output();
80 
81   if (!output.has_candidates()) {
82     return false;
83   }
84 
85   const commands::Candidates &candidates = output.candidates();
86   if (candidates.candidate_size() == 0) {
87     return false;
88   }
89 
90   return true;
91 }
92 
UpdateCandidateWindow(const commands::RendererCommand & command)93 Rect WindowManager::UpdateCandidateWindow(
94     const commands::RendererCommand &command) {
95   // TODO(nona): skip update if there are no changes.
96   DCHECK(command.has_output());
97   DCHECK(command.output().has_candidates());
98   const commands::Candidates &candidates = command.output().candidates();
99   DCHECK_LT(0, candidates.candidate_size());
100 
101   const Size new_window_size = candidate_window_->Update(candidates);
102 
103   Point new_window_pos = candidate_window_->GetWindowPos();
104   if (command.has_preedit_rectangle()) {
105     new_window_pos.x = command.preedit_rectangle().left();
106     new_window_pos.y = command.preedit_rectangle().bottom();
107   }
108 
109   const Rect working_area = GetMonitorRect(new_window_pos.x, new_window_pos.y);
110   const Point alignment_base_point_in_local_window_coord(
111       candidate_window_->GetCandidateColumnInClientCord().Left(), 0);
112   const auto &preedit_rect = command.preedit_rectangle();
113   const Rect caret_rect(preedit_rect.left(),
114                         preedit_rect.top(),
115                         preedit_rect.right() - preedit_rect.left(),
116                         preedit_rect.bottom() - preedit_rect.top());
117   // |caret_rect| is not always equal to preedit rect but can be an alternative
118   // in terms of positional calculation, especially for vertical adjustment in
119   // horizontal writing.
120   const Rect expected_window_rect_in_screen_coord =
121       WindowUtil::GetWindowRectForMainWindowFromTargetPointAndPreedit(
122           new_window_pos,
123           caret_rect,
124           new_window_size,
125           alignment_base_point_in_local_window_coord,
126           working_area,
127           false);  // GTK+ renderer only support horizontal window.
128   candidate_window_->Move(expected_window_rect_in_screen_coord.origin);
129   candidate_window_->ShowWindow();
130 
131   return expected_window_rect_in_screen_coord;
132 }
133 
134 // static
ShouldShowInfolistWindow(const commands::RendererCommand & command)135 bool WindowManager::ShouldShowInfolistWindow(
136     const commands::RendererCommand &command) {
137   if (!command.output().has_candidates()) {
138     return false;
139   }
140 
141   const commands::Candidates &candidates = command.output().candidates();
142   if (candidates.candidate_size() <= 0) {
143     return false;
144   }
145 
146   if (!candidates.has_usages() || !candidates.has_focused_index()) {
147     return false;
148   }
149 
150   if (candidates.usages().information_size() <=  0) {
151     return false;
152   }
153 
154   // Converts candidate's index to column row index.
155   const int focused_row
156       = candidates.focused_index() - candidates.candidate(0).index();
157   if (candidates.candidate_size() < focused_row) {
158     return false;
159   }
160 
161   if (!candidates.candidate(focused_row).has_information_id()) {
162     return false;
163   }
164 
165   return true;
166 }
167 
GetMonitorRect(gint x,gint y)168 Rect WindowManager::GetMonitorRect(gint x, gint y) {
169   GtkWidget *window = gtk_->GtkWindowNew(GTK_WINDOW_TOPLEVEL);
170   GdkScreen *screen = gtk_->GtkWindowGetScreen(window);
171   const gint monitor = gtk_->GdkScreenGetMonitorAtPoint(screen, x, y);
172   GdkRectangle screen_rect = {};
173   gtk_->GdkScreenGetMonitorGeometry(screen, monitor, &screen_rect);
174   return Rect(screen_rect.x, screen_rect.y,
175               screen_rect.width, screen_rect.height);
176 }
177 
UpdateInfolistWindow(const commands::RendererCommand & command,const Rect & candidate_window_rect)178 void WindowManager::UpdateInfolistWindow(
179     const commands::RendererCommand &command,
180     const Rect &candidate_window_rect) {
181 
182   if (!WindowManager::ShouldShowInfolistWindow(command)) {
183     infolist_window_->HideWindow();
184     return;
185   }
186 
187   const commands::Candidates &candidates = command.output().candidates();
188   const Size infolist_window_size = infolist_window_->Update(candidates);
189 
190   const Rect screen_rect = GetMonitorRect(candidate_window_rect.Left(),
191                                           candidate_window_rect.Top());
192   const Rect infolist_rect =
193       WindowUtil::WindowUtil::GetWindowRectForInfolistWindow(
194           infolist_window_size, candidate_window_rect, screen_rect);
195   infolist_window_->Move(infolist_rect.origin);
196   infolist_window_->ShowWindow();
197 }
198 
UpdateLayout(const commands::RendererCommand & command)199 void WindowManager::UpdateLayout(const commands::RendererCommand &command) {
200   if (!ShouldShowCandidateWindow(command)) {
201     HideAllWindows();
202     return;
203   }
204 
205   if (command.has_application_info() &&
206       command.application_info().has_pango_font_description()) {
207     const string font_description =
208         command.application_info().pango_font_description();
209     if (previous_font_description_ != font_description) {
210       DVLOG(1) << "Font description is changed"
211                << " From:" << previous_font_description_
212                << " To  :" << font_description;
213       candidate_window_->ReloadFontConfig(font_description);
214       infolist_window_->ReloadFontConfig(font_description);
215       previous_font_description_.assign(font_description);
216     }
217   }
218 
219   const Rect candidate_window_rect = UpdateCandidateWindow(command);
220   UpdateInfolistWindow(command, candidate_window_rect);
221 }
222 
Activate()223 bool WindowManager::Activate() {
224   // TODO(nona): Implement
225   return true;
226 }
227 
IsAvailable() const228 bool WindowManager::IsAvailable() const {
229   // TODO(nona): Implement
230   return true;
231 }
232 
SetSendCommandInterface(client::SendCommandInterface * send_command_interface)233 bool WindowManager::SetSendCommandInterface(
234     client::SendCommandInterface *send_command_interface) {
235   send_command_interface_ = send_command_interface;
236   return candidate_window_->SetSendCommandInterface(send_command_interface_) &&
237       infolist_window_->SetSendCommandInterface(send_command_interface_);
238 }
239 
SetWindowPos(int x,int y)240 void WindowManager::SetWindowPos(int x, int y) {
241   candidate_window_->Move(Point(x, y));
242 }
243 
244 }  // namespace gtk
245 }  // namespace renderer
246 }  // namespace mozc
247