OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 // |
| 5 // TODO(satorux): |
| 6 // - Implement a horizontal candidate window. |
| 7 // - Implement a scroll bar or an indicator showing where you are in the |
| 8 // candidate window. |
| 9 |
| 10 #include <algorithm> |
| 11 #include <string> |
| 12 #include <vector> |
| 13 |
| 14 #include "app/app_paths.h" |
| 15 #include "app/gfx/canvas.h" |
| 16 #include "app/gfx/font.h" |
| 17 #include "app/resource_bundle.h" |
| 18 #include "base/at_exit.h" |
| 19 #include "base/file_path.h" |
| 20 #include "base/observer_list.h" |
| 21 #include "base/path_service.h" |
| 22 #include "base/process_util.h" |
| 23 #include "base/scoped_ptr.h" |
| 24 #include "base/string_util.h" |
| 25 #include "chrome/browser/chromeos/cros/cros_library.h" |
| 26 #include "chrome/common/chrome_paths.h" |
| 27 #include "third_party/cros/chromeos_cros_api.h" |
| 28 #include "third_party/cros/chromeos_ime.h" |
| 29 #include "views/controls/label.h" |
| 30 #include "views/controls/textfield/textfield.h" |
| 31 #include "views/event.h" |
| 32 #include "views/fill_layout.h" |
| 33 #include "views/focus/accelerator_handler.h" |
| 34 #include "views/grid_layout.h" |
| 35 #include "views/screen.h" |
| 36 #include "views/widget/root_view.h" |
| 37 #include "views/widget/widget.h" |
| 38 #include "views/widget/widget_gtk.h" |
| 39 #include "views/window/non_client_view.h" |
| 40 #include "views/window/window.h" |
| 41 #include "views/window/window_delegate.h" |
| 42 |
| 43 namespace { |
| 44 |
| 45 // Colors used in the candidate window UI. |
| 46 const SkColor kFrameColor = SkColorSetRGB(0x96, 0x96, 0x96); |
| 47 const SkColor kShortcutBackgroundColor = SkColorSetARGB(0x10, 0x3, 0x4, 0xf); |
| 48 const SkColor kSelectedRowBackgroundColor = SkColorSetRGB(0xd1, 0xea, 0xff); |
| 49 const SkColor kDefaultBackgroundColor = SkColorSetRGB(0xff, 0xff, 0xff); |
| 50 const SkColor kSelectedRowFrameColor = SkColorSetRGB(0x7f, 0xac, 0xdd); |
| 51 const SkColor kFooterTopColor = SkColorSetRGB(0xff, 0xff, 0xff); |
| 52 const SkColor kFooterBottomColor = SkColorSetRGB(0xee, 0xee, 0xee); |
| 53 const SkColor kShortcutColor = SkColorSetRGB(0x61, 0x61, 0x61); |
| 54 const SkColor kDisabledShortcutColor = SkColorSetRGB(0xcc, 0xcc, 0xcc); |
| 55 |
| 56 // The minimum width of candidate labels in the vertical candidate |
| 57 // window. We use this value to prevent the candidate window from being |
| 58 // too narrow when all candidates are short. |
| 59 const int kMinCandidateLabelWidth = 100; |
| 60 |
| 61 // Wraps the given view with some padding, and returns it. |
| 62 views::View* WrapWithPadding(views::View* view, const gfx::Insets& insets) { |
| 63 views::View* wrapper = new views::View; |
| 64 // Use GridLayout to give some insets inside. |
| 65 views::GridLayout* layout = new views::GridLayout(wrapper); |
| 66 wrapper->SetLayoutManager(layout); // |wrapper| owns |layout|. |
| 67 layout->SetInsets(insets); |
| 68 |
| 69 views::ColumnSet* column_set = layout->AddColumnSet(0); |
| 70 column_set->AddColumn( |
| 71 views::GridLayout::FILL, views::GridLayout::FILL, |
| 72 1, views::GridLayout::USE_PREF, 0, 0); |
| 73 layout->StartRow(0, 0); |
| 74 |
| 75 // Add the view contents. |
| 76 layout->AddView(view); // |view| is owned by |wraper|, not |layout|. |
| 77 return wrapper; |
| 78 } |
| 79 |
| 80 } // namespace |
| 81 |
| 82 namespace chromeos { |
| 83 |
| 84 class CandidateView; |
| 85 |
| 86 // CandidateWindowView is the main container of the candidate window UI. |
| 87 class CandidateWindowView : public views::View { |
| 88 public: |
| 89 // Should we show candidates vertically or horizontally? |
| 90 enum Orientation { |
| 91 kVertical, |
| 92 kHorizontal, |
| 93 }; |
| 94 |
| 95 // The object can be monitored by the observer. |
| 96 class Observer { |
| 97 public: |
| 98 virtual ~Observer() {} |
| 99 // The function is called when a candidate is committed. |
| 100 // See comments at NotifyCandidateClicke() in chromeos_ime.h for |
| 101 // details about the parameters. |
| 102 virtual void OnCandidateCommitted(int index, int button, int flag) = 0; |
| 103 }; |
| 104 |
| 105 explicit CandidateWindowView(views::Widget* parent_frame); |
| 106 virtual ~CandidateWindowView() {} |
| 107 void Init(); |
| 108 |
| 109 // Adds the given observer. The ownership is not transferred. |
| 110 void AddObserver(Observer* observer) { |
| 111 observers_.AddObserver(observer); |
| 112 } |
| 113 |
| 114 // Removes the given observer. |
| 115 void RemoveObserver(Observer* observer) { |
| 116 observers_.RemoveObserver(observer); |
| 117 } |
| 118 |
| 119 // Selects the candidate specified by the index in the current page |
| 120 // (zero-origin). Changes the appearance of the selected candidate, |
| 121 // updates the information in the candidate window as needed. |
| 122 void SelectCandidateAt(int index_in_page); |
| 123 |
| 124 // The function is called when a candidate is being dragged. From the |
| 125 // given point, locates the candidate under the mouse cursor, and |
| 126 // selects it. |
| 127 void OnCandidateDragged(const gfx::Point& point); |
| 128 |
| 129 // Commits the candidate currently being selected. |
| 130 void CommitCandidate(); |
| 131 |
| 132 // Hides the auxiliary text. |
| 133 void HideAuxiliaryText(); |
| 134 |
| 135 // Shows the auxiliary text. |
| 136 void ShowAuxiliaryText(); |
| 137 |
| 138 // Updates the auxiliary text. |
| 139 void UpdateAuxiliaryText(const std::string& utf8_text); |
| 140 |
| 141 // Updates candidates of the candidate window from |lookup_table|. |
| 142 void UpdateCandidates(const ImeLookupTable& lookup_table); |
| 143 |
| 144 private: |
| 145 // Initializes the candidate views if needed. |
| 146 void MaybeInitializeCandidateViews(int num_views, |
| 147 Orientation orientation); |
| 148 |
| 149 // Creates the footer area, where we show status information. |
| 150 // For instance, we show a cursor position like 2/19. |
| 151 views::View* CreateFooterArea(); |
| 152 |
| 153 // Creates the header area, where we show auxiliary text. |
| 154 views::View* CreateHeaderArea(); |
| 155 |
| 156 // Resizes the parent frame and schedules painting. This needs to be |
| 157 // called when the visible contents of the candidate window are |
| 158 // modified. |
| 159 void ResizeAndSchedulePaint(); |
| 160 |
| 161 // The orientation of the candidate window. |
| 162 Orientation orientation_; |
| 163 |
| 164 // The lookup table (candidates). |
| 165 ImeLookupTable lookup_table_; |
| 166 // The index in the current page of the candidate currently being selected. |
| 167 int selected_candidate_index_in_page_; |
| 168 // The observers of the object. |
| 169 ObserverList<Observer> observers_; |
| 170 |
| 171 // The parent frame. |
| 172 views::Widget* parent_frame_; |
| 173 |
| 174 // Views created in the class will be part of tree of |this|, so these |
| 175 // child views will be deleted when |this| is deleted. |
| 176 |
| 177 // The candidate area is where candidates are rendered. |
| 178 views::View* candidate_area_; |
| 179 // The footer area is where some status messages are shown if needed. |
| 180 views::View* footer_area_; |
| 181 // We use this when we show something in the footer area. |
| 182 scoped_ptr<views::View> footer_area_contents_; |
| 183 // We use this when we show nothing in the footer area. |
| 184 scoped_ptr<views::View> footer_area_place_holder_; |
| 185 // The header area is where the auxiliary text is shown, if the |
| 186 // auxiliary text is provided. If it is not provided, we show nothing. |
| 187 // For instance, we show pinyin text like "zhong'guo", but we show |
| 188 // nothing with Japanese IME. |
| 189 views::View* header_area_; |
| 190 // We use this when we show something in the header area. |
| 191 scoped_ptr<views::View> header_area_contents_; |
| 192 // We use this when we show nothing in the header area. |
| 193 scoped_ptr<views::View> header_area_place_holder_; |
| 194 // The candidate views are used for rendering candidates. |
| 195 std::vector<CandidateView*> candidate_views_; |
| 196 // The auxiliary text label is shown in the auxiliary text area. |
| 197 views::Label* auxiliary_text_label_; |
| 198 // The footer label is shown in the footer area. |
| 199 views::Label* footer_label_; |
| 200 }; |
| 201 |
| 202 // CandidateRow renderes a row of a candidate. |
| 203 class CandidateView : public views::View { |
| 204 public: |
| 205 CandidateView(CandidateWindowView* parent_candidate_window, |
| 206 int index_in_page, |
| 207 CandidateWindowView::Orientation orientation); |
| 208 virtual ~CandidateView() {} |
| 209 void Init(); |
| 210 |
| 211 // Sets candidate text with the given text. |
| 212 void SetCandidateText(const std::wstring& text); |
| 213 |
| 214 // Selects the candidate row. Changes the appearance to make it look |
| 215 // like a selected candidate. |
| 216 void Select(); |
| 217 |
| 218 // Unselects the candidate row. Changes the appearance to make it look |
| 219 // like an unselected candidate. |
| 220 void Unselect(); |
| 221 |
| 222 // Disables the candidate row. Changes the appearance to make it look |
| 223 // like unclickable area. |
| 224 void Disable(); |
| 225 |
| 226 private: |
| 227 // View::OnMousePressed() implementation. |
| 228 virtual bool OnMousePressed(const views::MouseEvent& event); |
| 229 |
| 230 // View::OnMouseDragged() implementation. |
| 231 virtual bool OnMouseDragged(const views::MouseEvent& event); |
| 232 |
| 233 // View::OnMouseReleased() implementation. |
| 234 virtual void OnMouseReleased(const views::MouseEvent& event, |
| 235 bool canceled); |
| 236 |
| 237 // Zero-origin index in the current page. |
| 238 int index_in_page_; |
| 239 |
| 240 // The orientation of the candidate view. |
| 241 CandidateWindowView::Orientation orientation_; |
| 242 |
| 243 // The parent candidate window that contains this view. |
| 244 CandidateWindowView* parent_candidate_window_; |
| 245 |
| 246 // Views created in the class will be part of tree of |this|, so these |
| 247 // child views will be deleted when |this| is deleted. |
| 248 |
| 249 // The shortcut label renders shortcut numbers like 1, 2, and 3. |
| 250 views::Label* shortcut_label_; |
| 251 // The candidate label renders candidates. |
| 252 views::Label* candidate_label_; |
| 253 }; |
| 254 |
| 255 // VerticalCandidateLabel is used for rendering candidate text in |
| 256 // the vertical candidate window. |
| 257 class VerticalCandidateLabel : public views::Label { |
| 258 virtual ~VerticalCandidateLabel() {} |
| 259 |
| 260 // Returns the preferred size, but guarantees that the width has at |
| 261 // least kMinCandidateLabelWidth pixels. |
| 262 virtual gfx::Size GetPreferredSize() { |
| 263 gfx::Size size = Label::GetPreferredSize(); |
| 264 // Hack. +2 is needed to prevent labels from getting elided like |
| 265 // "abc..." in some cases. TODO(satorux): Figure out why it's |
| 266 // necessary. |
| 267 size.set_width(size.width() + 2); |
| 268 if (size.width() < kMinCandidateLabelWidth) { |
| 269 size.set_width(kMinCandidateLabelWidth); |
| 270 } |
| 271 return size; |
| 272 } |
| 273 }; |
| 274 |
| 275 // CandidateWindowController controls the CandidateWindow. |
| 276 class CandidateWindowController : public CandidateWindowView::Observer { |
| 277 public: |
| 278 CandidateWindowController(); |
| 279 virtual ~CandidateWindowController(); |
| 280 void Init(); |
| 281 |
| 282 // Returns the work area of the monitor nearest the candidate window. |
| 283 gfx::Rect GetMonitorWorkAreaNearestWindow(); |
| 284 |
| 285 // CandidateWindowView::Observer implementation. |
| 286 virtual void OnCandidateCommitted(int index, |
| 287 int button, |
| 288 int flags); |
| 289 |
| 290 private: |
| 291 // Creates the candidate window view. |
| 292 void CreateView(); |
| 293 |
| 294 // The function is called when |HideAuxiliaryText| signal is received in |
| 295 // libcros. |ime_library| is a void pointer to this object. |
| 296 static void OnHideAuxiliaryText(void* ime_library); |
| 297 |
| 298 // The function is called when |HideLookupTable| signal is received in |
| 299 // libcros. |ime_library| is a void pointer to this object. |
| 300 static void OnHideLookupTable(void* ime_library); |
| 301 |
| 302 // The function is called when |SetCursorLocation| signal is received |
| 303 // in libcros. |ime_library| is a void pointer to this object. |
| 304 static void OnSetCursorLocation(void* ime_library, |
| 305 int x, |
| 306 int y, |
| 307 int width, |
| 308 int height); |
| 309 |
| 310 // The function is called when |UpdateAuxiliaryText| signal is received |
| 311 // in libcros. |ime_library| is a void pointer to this object. |
| 312 static void OnUpdateAuxiliaryText(void* ime_library, |
| 313 const std::string& utf8_text, |
| 314 bool visible); |
| 315 |
| 316 // The function is called when |UpdateLookupTable| signal is received |
| 317 // in libcros. |ime_library| is a void pointer to this object. |
| 318 static void OnUpdateLookupTable(void* ime_library, |
| 319 const ImeLookupTable& lookup_table); |
| 320 |
| 321 // The connection is used for communicating with IME code in libcros. |
| 322 ImeStatusConnection* ime_status_connection_; |
| 323 |
| 324 // The candidate window view. |
| 325 CandidateWindowView* candidate_window_; |
| 326 |
| 327 // This is the outer frame of the candidate window view. The frame will |
| 328 // own |candidate_window_|. |
| 329 scoped_ptr<views::Widget> frame_; |
| 330 }; |
| 331 |
| 332 CandidateView::CandidateView( |
| 333 CandidateWindowView* parent_candidate_window, |
| 334 int index_in_page, |
| 335 CandidateWindowView::Orientation orientation) |
| 336 : index_in_page_(index_in_page), |
| 337 orientation_(orientation), |
| 338 parent_candidate_window_(parent_candidate_window), |
| 339 shortcut_label_(NULL), |
| 340 candidate_label_(NULL) { |
| 341 } |
| 342 |
| 343 void CandidateView::Init() { |
| 344 views::GridLayout* layout = new views::GridLayout(this); |
| 345 SetLayoutManager(layout); // |this| owns |layout|. |
| 346 |
| 347 // Choose the character used for the shortcut label. |
| 348 const wchar_t kShortcutCharacters[] = L"1234567890ABCDEF"; |
| 349 // The default character should not be used but just in case. |
| 350 wchar_t shortcut_character = L'?'; |
| 351 if (index_in_page_ < static_cast<int>(arraysize(kShortcutCharacters) - 1)) { |
| 352 shortcut_character = kShortcutCharacters[index_in_page_]; |
| 353 } |
| 354 |
| 355 // Create the shortcut label. The label will eventually be part of the |
| 356 // tree of |this| via |wrapped_shortcut_label|, hence it's deleted when |
| 357 // |this| is deleted. |
| 358 shortcut_label_ = new views::Label( |
| 359 StringPrintf(L"%lc", shortcut_character)); |
| 360 |
| 361 // Wrap it with padding. |
| 362 const gfx::Insets kVerticalShortcutLabelInsets(1, 6, 1, 6); |
| 363 const gfx::Insets kHorizontalShortcutLabelInsets(1, 1, 1, 1); |
| 364 const gfx::Insets insets = (orientation_ == CandidateWindowView::kVertical ? |
| 365 kVerticalShortcutLabelInsets : |
| 366 kHorizontalShortcutLabelInsets); |
| 367 views::View* wrapped_shortcut_label = |
| 368 WrapWithPadding(shortcut_label_, insets); |
| 369 // Make the font bold. |
| 370 gfx::Font font = shortcut_label_->GetFont(); |
| 371 gfx::Font bold_font = font.DeriveFont(0, gfx::Font::BOLD); |
| 372 shortcut_label_->SetFont(bold_font); |
| 373 // TODO(satorux): Maybe we need to use language specific fonts for |
| 374 // candidate_label, like Chinese font for Chinese IME? |
| 375 |
| 376 // Add decoration based on the orientation. |
| 377 if (orientation_ == CandidateWindowView::kVertical) { |
| 378 // Set the background color. |
| 379 wrapped_shortcut_label->set_background( |
| 380 views::Background::CreateSolidBackground( |
| 381 kShortcutBackgroundColor)); |
| 382 } |
| 383 shortcut_label_->SetColor(kShortcutColor); |
| 384 |
| 385 // Create the candidate label. The label will be added to |this| as a |
| 386 // child view, hence it's deleted when |this| is deleted. |
| 387 if (orientation_ == CandidateWindowView::kVertical) { |
| 388 candidate_label_ = new VerticalCandidateLabel; |
| 389 } else { |
| 390 candidate_label_ = new views::Label; |
| 391 } |
| 392 candidate_label_->SetHorizontalAlignment(views::Label::ALIGN_LEFT); |
| 393 |
| 394 // Initialize the column set with two columns. |
| 395 views::ColumnSet* column_set = layout->AddColumnSet(0); |
| 396 column_set->AddColumn(views::GridLayout::LEADING, views::GridLayout::LEADING, |
| 397 0, views::GridLayout::USE_PREF, 0, 0); |
| 398 column_set->AddPaddingColumn(0, 4); |
| 399 column_set->AddColumn(views::GridLayout::LEADING, views::GridLayout::LEADING, |
| 400 0, views::GridLayout::USE_PREF, 0, 0); |
| 401 column_set->AddPaddingColumn(0, 4); |
| 402 |
| 403 // Add the shortcut label and the candidate label. |
| 404 layout->StartRow(0, 0); |
| 405 // |wrapped_shortcut_label| and |candidate_label_| will be owned by |this|. |
| 406 layout->AddView(wrapped_shortcut_label); |
| 407 layout->AddView(candidate_label_); |
| 408 } |
| 409 |
| 410 void CandidateView::SetCandidateText(const std::wstring& text) { |
| 411 shortcut_label_->SetColor(kShortcutColor); |
| 412 candidate_label_->SetText(text); |
| 413 } |
| 414 |
| 415 void CandidateView::Select() { |
| 416 set_background( |
| 417 views::Background::CreateSolidBackground(kSelectedRowBackgroundColor)); |
| 418 set_border(views::Border::CreateSolidBorder(1, kSelectedRowFrameColor)); |
| 419 } |
| 420 |
| 421 void CandidateView::Unselect() { |
| 422 set_background(NULL); |
| 423 set_border(NULL); |
| 424 } |
| 425 |
| 426 void CandidateView::Disable() { |
| 427 shortcut_label_->SetColor(kDisabledShortcutColor); |
| 428 candidate_label_->SetText(L""); |
| 429 } |
| 430 |
| 431 bool CandidateView::OnMousePressed(const views::MouseEvent& event) { |
| 432 // Select the candidate. We'll commit the candidate when the mouse |
| 433 // button is released. |
| 434 parent_candidate_window_->SelectCandidateAt(index_in_page_); |
| 435 // Request MouseDraggged and MouseReleased events. |
| 436 return true; |
| 437 } |
| 438 |
| 439 bool CandidateView::OnMouseDragged(const views::MouseEvent& event) { |
| 440 gfx::Point location_in_candidate_window = event.location(); |
| 441 views::View::ConvertPointToView(this, parent_candidate_window_, |
| 442 &location_in_candidate_window); |
| 443 // Notify the candidate window that a candidate is now being dragged. |
| 444 parent_candidate_window_->OnCandidateDragged(location_in_candidate_window); |
| 445 // Request MouseReleased event. |
| 446 return true; |
| 447 } |
| 448 |
| 449 void CandidateView::OnMouseReleased(const views::MouseEvent& event, |
| 450 bool canceled) { |
| 451 // Commit the current candidate unless it's canceled. |
| 452 if (!canceled) { |
| 453 parent_candidate_window_->CommitCandidate(); |
| 454 } |
| 455 } |
| 456 |
| 457 CandidateWindowView::CandidateWindowView( |
| 458 views::Widget* parent_frame) |
| 459 : orientation_(kVertical), |
| 460 selected_candidate_index_in_page_(0), |
| 461 parent_frame_(parent_frame), |
| 462 candidate_area_(NULL), |
| 463 footer_area_(NULL), |
| 464 header_area_(NULL), |
| 465 auxiliary_text_label_(NULL), |
| 466 footer_label_(NULL) { |
| 467 } |
| 468 |
| 469 void CandidateWindowView::Init() { |
| 470 // Set the background and the border of the view. |
| 471 set_background( |
| 472 views::Background::CreateSolidBackground(kDefaultBackgroundColor)); |
| 473 set_border(views::Border::CreateSolidBorder(1, kFrameColor)); |
| 474 |
| 475 // Create the header area. |
| 476 header_area_ = CreateHeaderArea(); |
| 477 // Create the candidate area. |
| 478 candidate_area_ = new views::View; |
| 479 // Create the footer area. |
| 480 footer_area_ = CreateFooterArea(); |
| 481 |
| 482 // Set the window layout of the view |
| 483 views::GridLayout* layout = new views::GridLayout(this); |
| 484 SetLayoutManager(layout); // |this| owns layout|. |
| 485 views::ColumnSet* column_set = layout->AddColumnSet(0); |
| 486 column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, |
| 487 0, views::GridLayout::USE_PREF, 0, 0); |
| 488 |
| 489 // Add the header area. |
| 490 layout->StartRow(0, 0); |
| 491 layout->AddView(header_area_); // |header_area_| is owned by |this|. |
| 492 |
| 493 // Add the candidate area. |
| 494 layout->StartRow(0, 0); |
| 495 layout->AddView(candidate_area_); // |candidate_area_| is owned by |this|. |
| 496 |
| 497 // Add the footer area. |
| 498 layout->StartRow(0, 0); |
| 499 layout->AddView(footer_area_); // |footer_area_| is owned by |this|. |
| 500 } |
| 501 |
| 502 void CandidateWindowView::HideAuxiliaryText() { |
| 503 // Put the place holder to the header area. |
| 504 header_area_->RemoveAllChildViews(false); // Don't delete child views. |
| 505 header_area_->AddChildView(header_area_place_holder_.get()); |
| 506 ResizeAndSchedulePaint(); |
| 507 } |
| 508 |
| 509 void CandidateWindowView::ShowAuxiliaryText() { |
| 510 // Put contents to the header area. |
| 511 header_area_->RemoveAllChildViews(false); // Don't delete child views. |
| 512 header_area_->AddChildView(header_area_contents_.get()); |
| 513 ResizeAndSchedulePaint(); |
| 514 } |
| 515 |
| 516 void CandidateWindowView::UpdateAuxiliaryText(const std::string& utf8_text) { |
| 517 auxiliary_text_label_->SetText(UTF8ToWide(utf8_text)); |
| 518 } |
| 519 |
| 520 void CandidateWindowView::UpdateCandidates( |
| 521 const ImeLookupTable& lookup_table) { |
| 522 // HACK: ibus-pinyin sets page_size to 5. For now, we use the magic |
| 523 // number here to determine the orientation. |
| 524 // TODO(satorux): We should get the orientation information from |
| 525 // lookup_table. |
| 526 Orientation orientation = kVertical; |
| 527 if (lookup_table.page_size == 5) { |
| 528 orientation = kHorizontal; |
| 529 } |
| 530 // Initialize candidate views if necessary. |
| 531 MaybeInitializeCandidateViews(lookup_table.page_size, |
| 532 orientation); |
| 533 lookup_table_ = lookup_table; |
| 534 orientation_ = orientation; |
| 535 |
| 536 // Update the candidates in the current page. |
| 537 const int start_from = (lookup_table_.current_page_index * |
| 538 lookup_table_.page_size); |
| 539 for (size_t i = 0; i < candidate_views_.size(); ++i) { |
| 540 const size_t index_in_page = i; |
| 541 const size_t candidate_index = start_from + index_in_page; |
| 542 if (candidate_index < lookup_table_.candidates.size()) { |
| 543 candidate_views_[index_in_page]->SetCandidateText( |
| 544 UTF8ToWide(lookup_table_.candidates[candidate_index])); |
| 545 } else { |
| 546 // Disable the empty row. |
| 547 candidate_views_[index_in_page]->Disable(); |
| 548 } |
| 549 } |
| 550 |
| 551 // Select the current candidate per the lookup table. |
| 552 // TODO(satorux): Rename cursor_row_index to cursor_index_in_page. |
| 553 SelectCandidateAt(lookup_table_.cursor_row_index); |
| 554 } |
| 555 |
| 556 void CandidateWindowView::MaybeInitializeCandidateViews( |
| 557 int num_views, |
| 558 Orientation orientation) { |
| 559 // If the requested number of views matches the number of current views, |
| 560 // just reuse these. |
| 561 if (num_views == static_cast<int>(candidate_views_.size()) && |
| 562 orientation == orientation_) { |
| 563 return; |
| 564 } |
| 565 |
| 566 // Clear the existing candidate_views if any. |
| 567 for (size_t i = 0; i < candidate_views_.size(); ++i) { |
| 568 candidate_area_->RemoveChildView(candidate_views_[i]); |
| 569 } |
| 570 candidate_views_.clear(); |
| 571 |
| 572 views::GridLayout* layout = new views::GridLayout(candidate_area_); |
| 573 // |candidate_area_| owns |layout|. |
| 574 candidate_area_->SetLayoutManager(layout); |
| 575 // Initialize the column set. |
| 576 views::ColumnSet* column_set = layout->AddColumnSet(0); |
| 577 if (orientation == kVertical) { |
| 578 column_set->AddColumn(views::GridLayout::FILL, |
| 579 views::GridLayout::FILL, |
| 580 0, views::GridLayout::USE_PREF, 0, 0); |
| 581 } else { |
| 582 for (int i = 0; i < num_views; ++i) { |
| 583 column_set->AddColumn(views::GridLayout::FILL, |
| 584 views::GridLayout::FILL, |
| 585 0, views::GridLayout::USE_PREF, 0, 0); |
| 586 } |
| 587 } |
| 588 |
| 589 // Set insets so the border of the selected candidate is drawn inside of |
| 590 // the border of the main candidate window, but we don't have the inset |
| 591 // at the top and the bottom as we have the borders of the header and |
| 592 // footer areas. |
| 593 const gfx::Insets kCandidateAreaInsets(0, 1, 0, 1); |
| 594 layout->SetInsets(kCandidateAreaInsets.top(), |
| 595 kCandidateAreaInsets.left(), |
| 596 kCandidateAreaInsets.bottom(), |
| 597 kCandidateAreaInsets.right()); |
| 598 |
| 599 // Add views to the candidate area. |
| 600 if (orientation == kHorizontal) { |
| 601 layout->StartRow(0, 0); |
| 602 } |
| 603 for (int i = 0; i < num_views; ++i) { |
| 604 CandidateView* candidate_row = new CandidateView(this, i, orientation); |
| 605 candidate_row->Init(); |
| 606 candidate_views_.push_back(candidate_row); |
| 607 if (orientation == kVertical) { |
| 608 layout->StartRow(0, 0); |
| 609 } |
| 610 // |candidate_row| will be owned by candidate_area_|. |
| 611 layout->AddView(candidate_row); |
| 612 } |
| 613 } |
| 614 |
| 615 views::View* CandidateWindowView::CreateHeaderArea() { |
| 616 // |header_area_place_holder_| will not be owned by another view. |
| 617 // This will be deleted by scoped_ptr. |
| 618 // |
| 619 // This is because we swap the contents of |header_area_| between |
| 620 // |header_area_place_holder_| (to show nothing) and |
| 621 // |header_area_contents_| (to show something). In other words, |
| 622 // |header_area_| only contains one of the two views hence cannot own |
| 623 // the two views at the same time. |
| 624 header_area_place_holder_.reset(new views::View); |
| 625 header_area_place_holder_->set_parent_owned(false); // Won't be owened. |
| 626 |
| 627 // |auxiliary_text_label_| will be owned by |header_area_contents_|. |
| 628 auxiliary_text_label_ = new views::Label; |
| 629 auxiliary_text_label_->SetHorizontalAlignment(views::Label::ALIGN_LEFT); |
| 630 |
| 631 const gfx::Insets kHeaderInsets(2, 2, 2, 4); |
| 632 // |header_area_contents_| will not be owned by another view. |
| 633 // See a comment at |header_area_place_holder_| for why. |
| 634 header_area_contents_.reset( |
| 635 WrapWithPadding(auxiliary_text_label_, kHeaderInsets)); |
| 636 header_area_contents_->set_parent_owned(false); // Won't be owened. |
| 637 header_area_contents_->set_border( |
| 638 views::Border::CreateSolidBorder(1, kFrameColor)); |
| 639 header_area_contents_->set_background( |
| 640 views::Background::CreateVerticalGradientBackground( |
| 641 kFooterTopColor, |
| 642 kFooterBottomColor)); |
| 643 |
| 644 views::View* header_area = new views::View; |
| 645 header_area->SetLayoutManager(new views::FillLayout); |
| 646 // Initialize the header area with the place holder (i.e. show nothing). |
| 647 header_area->AddChildView(header_area_place_holder_.get()); |
| 648 return header_area; |
| 649 } |
| 650 |
| 651 views::View* CandidateWindowView::CreateFooterArea() { |
| 652 // |footer_area_place_holder_| will not be owned by another view. |
| 653 // See also the comment about |header_area_place_holder_| in |
| 654 // CreateHeaderArea(). |
| 655 footer_area_place_holder_.reset(new views::View); |
| 656 footer_area_place_holder_->set_parent_owned(false); // Won't be owened. |
| 657 |
| 658 footer_label_ = new views::Label(); |
| 659 footer_label_->SetHorizontalAlignment(views::Label::ALIGN_RIGHT); |
| 660 |
| 661 const gfx::Insets kFooterInsets(2, 2, 2, 4); |
| 662 footer_area_contents_.reset( |
| 663 WrapWithPadding(footer_label_, kFooterInsets)); |
| 664 footer_area_contents_->set_parent_owned(false); // Won't be owened. |
| 665 footer_area_contents_->set_border( |
| 666 views::Border::CreateSolidBorder(1, kFrameColor)); |
| 667 footer_area_contents_->set_background( |
| 668 views::Background::CreateVerticalGradientBackground( |
| 669 kFooterTopColor, |
| 670 kFooterBottomColor)); |
| 671 |
| 672 views::View* footer_area = new views::View; |
| 673 footer_area->SetLayoutManager(new views::FillLayout); |
| 674 // Initialize the footer area with the place holder (i.e. show nothing). |
| 675 footer_area->AddChildView(footer_area_place_holder_.get()); |
| 676 return footer_area; |
| 677 } |
| 678 |
| 679 void CandidateWindowView::SelectCandidateAt(int index_in_page) { |
| 680 // Ignore click on out of range views. |
| 681 if (index_in_page >= lookup_table_.num_candidates_in_current_page) { |
| 682 return; |
| 683 } |
| 684 |
| 685 // Remember the currently selected candidate index in the current page. |
| 686 selected_candidate_index_in_page_ = index_in_page; |
| 687 |
| 688 // Unselect all the candidate first. Theoretically, we could remember |
| 689 // the lastly selected candidate and only unselect it, but unselecting |
| 690 // everything is simpler. |
| 691 for (size_t i = 0; i < candidate_views_.size(); ++i) { |
| 692 candidate_views_[i]->Unselect(); |
| 693 } |
| 694 // Select the candidate specified by index_in_page. |
| 695 candidate_views_[index_in_page]->Select(); |
| 696 |
| 697 // Update the cursor indexes in the model. |
| 698 lookup_table_.cursor_row_index = index_in_page; |
| 699 lookup_table_.cursor_absolute_index = |
| 700 (lookup_table_.page_size * lookup_table_.current_page_index + |
| 701 index_in_page); |
| 702 |
| 703 // Update the footer area. |
| 704 footer_area_->RemoveAllChildViews(false); // Don't delete child views. |
| 705 if (orientation_ == kVertical) { |
| 706 // Show information about the cursor and the page in the footer area. |
| 707 footer_label_->SetText( |
| 708 StringPrintf(L"%d/%d", |
| 709 lookup_table_.cursor_absolute_index + 1, |
| 710 lookup_table_.candidates.size())); |
| 711 footer_area_->AddChildView(footer_area_contents_.get()); |
| 712 } else { |
| 713 // Show nothing in the footer area if the orientation is horizontal. |
| 714 footer_area_->AddChildView(footer_area_place_holder_.get()); |
| 715 } |
| 716 |
| 717 ResizeAndSchedulePaint(); |
| 718 } |
| 719 |
| 720 void CandidateWindowView::OnCandidateDragged( |
| 721 const gfx::Point& location) { |
| 722 for (size_t i = 0; i < candidate_views_.size(); ++i) { |
| 723 gfx::Point converted_location = location; |
| 724 views::View::ConvertPointToView(this, candidate_views_[i], |
| 725 &converted_location); |
| 726 if (candidate_views_[i]->HitTest(converted_location)) { |
| 727 SelectCandidateAt(i); |
| 728 break; |
| 729 } |
| 730 } |
| 731 } |
| 732 |
| 733 void CandidateWindowView::CommitCandidate() { |
| 734 // For now, we don't distinguish left and right clicks. |
| 735 const int button = 1; // Left button. |
| 736 const int key_modifilers = 0; |
| 737 FOR_EACH_OBSERVER(Observer, observers_, |
| 738 OnCandidateCommitted(selected_candidate_index_in_page_, |
| 739 button, |
| 740 key_modifilers)); |
| 741 } |
| 742 |
| 743 void CandidateWindowView::ResizeAndSchedulePaint() { |
| 744 // Resize the parent frame, with the current candidate window size. |
| 745 gfx::Size size = GetPreferredSize(); |
| 746 gfx::Rect bounds; |
| 747 parent_frame_->GetBounds(&bounds, false); |
| 748 bounds.set_width(size.width()); |
| 749 bounds.set_height(size.height()); |
| 750 parent_frame_->SetBounds(bounds); |
| 751 |
| 752 SchedulePaint(); |
| 753 } |
| 754 |
| 755 void CandidateWindowController::Init() { |
| 756 // Initialize the IME status connection. |
| 757 ImeStatusMonitorFunctions functions; |
| 758 functions.hide_auxiliary_text = |
| 759 &CandidateWindowController::OnHideAuxiliaryText; |
| 760 functions.hide_lookup_table = |
| 761 &CandidateWindowController::OnHideLookupTable; |
| 762 functions.set_cursor_location = |
| 763 &CandidateWindowController::OnSetCursorLocation; |
| 764 functions.update_auxiliary_text = |
| 765 &CandidateWindowController::OnUpdateAuxiliaryText; |
| 766 functions.update_lookup_table = |
| 767 &CandidateWindowController::OnUpdateLookupTable; |
| 768 ime_status_connection_ = MonitorImeStatus(functions, this); |
| 769 CHECK(ime_status_connection_) |
| 770 << "MonitorImeStatus() failed."; |
| 771 |
| 772 // Create the candidate window view. |
| 773 CreateView(); |
| 774 } |
| 775 |
| 776 void CandidateWindowController::CreateView() { |
| 777 // Create a non-decorated frame. |
| 778 frame_.reset(views::Widget::CreatePopupWidget( |
| 779 views::Widget::NotTransparent, |
| 780 views::Widget::AcceptEvents, |
| 781 views::Widget::DeleteOnDestroy)); |
| 782 // The size is initially zero. |
| 783 frame_->Init(NULL, gfx::Rect(0, 0)); |
| 784 |
| 785 // Create the candidate window. |
| 786 candidate_window_ = new CandidateWindowView(frame_.get()); |
| 787 candidate_window_->Init(); |
| 788 candidate_window_->AddObserver(this); |
| 789 |
| 790 // Put the candidate window view on the frame. The frame is resized |
| 791 // later when the candidate window is shown. |
| 792 views::RootView* root_view = frame_->GetRootView(); |
| 793 // |root_view| owns the |candidate_window_|, thus |frame_| effectively |
| 794 // owns |candidate_window_|. |
| 795 root_view->SetContentsView(candidate_window_); |
| 796 } |
| 797 |
| 798 CandidateWindowController::CandidateWindowController() |
| 799 : ime_status_connection_(NULL), |
| 800 frame_(NULL) { |
| 801 } |
| 802 |
| 803 CandidateWindowController::~CandidateWindowController() { |
| 804 candidate_window_->RemoveObserver(this); |
| 805 chromeos::DisconnectImeStatus(ime_status_connection_); |
| 806 } |
| 807 |
| 808 gfx::Rect CandidateWindowController::GetMonitorWorkAreaNearestWindow() { |
| 809 return views::Screen::GetMonitorWorkAreaNearestWindow( |
| 810 frame_->GetNativeView()); |
| 811 } |
| 812 |
| 813 void CandidateWindowController::OnHideAuxiliaryText(void* ime_library) { |
| 814 CandidateWindowController* controller = |
| 815 static_cast<CandidateWindowController*>(ime_library); |
| 816 |
| 817 controller->candidate_window_->HideAuxiliaryText(); |
| 818 } |
| 819 |
| 820 void CandidateWindowController::OnHideLookupTable(void* ime_library) { |
| 821 CandidateWindowController* controller = |
| 822 static_cast<CandidateWindowController*>(ime_library); |
| 823 |
| 824 controller->frame_->Hide(); |
| 825 } |
| 826 |
| 827 void CandidateWindowController::OnSetCursorLocation(void* ime_library, |
| 828 int x, |
| 829 int y, |
| 830 int width, |
| 831 int height) { |
| 832 CandidateWindowController* controller = |
| 833 static_cast<CandidateWindowController*>(ime_library); |
| 834 |
| 835 // TODO(satorux): This has to be computed runtime. |
| 836 const int kHorizontalOffset = 30; |
| 837 |
| 838 gfx::Rect frame_bounds; |
| 839 controller->frame_->GetBounds(&frame_bounds, false); |
| 840 |
| 841 gfx::Rect screen_bounds = |
| 842 controller->GetMonitorWorkAreaNearestWindow(); |
| 843 |
| 844 // The default position. |
| 845 frame_bounds.set_x(x - kHorizontalOffset); |
| 846 frame_bounds.set_y(y + height); |
| 847 |
| 848 // Handle overflow at the left and the top. |
| 849 frame_bounds.set_x(std::max(frame_bounds.x(), screen_bounds.x())); |
| 850 frame_bounds.set_y(std::max(frame_bounds.y(), screen_bounds.y())); |
| 851 |
| 852 // Handle overflow at the right. |
| 853 const int right_overflow = frame_bounds.right() - screen_bounds.right(); |
| 854 if (right_overflow > 0) { |
| 855 frame_bounds.set_x(frame_bounds.x() - right_overflow); |
| 856 } |
| 857 |
| 858 // Handle overflow at the bottom. |
| 859 const int bottom_overflow = frame_bounds.bottom() - screen_bounds.bottom(); |
| 860 if (bottom_overflow > 0) { |
| 861 frame_bounds.set_y(frame_bounds.y() - height - frame_bounds.height()); |
| 862 } |
| 863 |
| 864 controller->frame_->SetBounds(frame_bounds); |
| 865 } |
| 866 |
| 867 void CandidateWindowController::OnUpdateAuxiliaryText( |
| 868 void* ime_library, |
| 869 const std::string& utf8_text, |
| 870 bool visible) { |
| 871 CandidateWindowController* controller = |
| 872 static_cast<CandidateWindowController*>(ime_library); |
| 873 // HACK for ibus-anthy: ibus-anthy sends us page information like |
| 874 // "( 1 / 19 )" as auxiliary text. We should ignore this as we show the |
| 875 // same information in the footer area (i.e. don't want to show the same |
| 876 // information in two places). |
| 877 // |
| 878 // TODO(satorux): Remove this once we remove ibus-anthy from Chromium OS. |
| 879 if (utf8_text.size() >= 2 && |
| 880 utf8_text[0] == '(' && utf8_text[utf8_text.size() -1] == ')') { |
| 881 // Hide the auxiliary text in case something is shown already. |
| 882 controller->candidate_window_->HideAuxiliaryText(); |
| 883 return; // Ignore the given auxiliary text. |
| 884 } |
| 885 |
| 886 // If it's not visible, hide the auxiliary text and return. |
| 887 if (!visible) { |
| 888 controller->candidate_window_->HideAuxiliaryText(); |
| 889 return; |
| 890 } |
| 891 controller->candidate_window_->UpdateAuxiliaryText(utf8_text); |
| 892 controller->candidate_window_->ShowAuxiliaryText(); |
| 893 } |
| 894 |
| 895 void CandidateWindowController::OnUpdateLookupTable( |
| 896 void* ime_library, |
| 897 const ImeLookupTable& lookup_table) { |
| 898 CandidateWindowController* controller = |
| 899 static_cast<CandidateWindowController*>(ime_library); |
| 900 |
| 901 // If it's not visible, hide the window and return. |
| 902 if (!lookup_table.visible) { |
| 903 controller->frame_->Hide(); |
| 904 return; |
| 905 } |
| 906 |
| 907 controller->candidate_window_->UpdateCandidates(lookup_table); |
| 908 controller->frame_->Show(); |
| 909 } |
| 910 |
| 911 void CandidateWindowController::OnCandidateCommitted(int index, |
| 912 int button, |
| 913 int flags) { |
| 914 NotifyCandidateClicked(ime_status_connection_, index, button, flags); |
| 915 } |
| 916 |
| 917 } // namespace chromeos |
| 918 |
| 919 int main(int argc, char** argv) { |
| 920 // Initialize gtk stuff. |
| 921 g_thread_init(NULL); |
| 922 g_type_init(); |
| 923 gtk_init(&argc, &argv); |
| 924 |
| 925 // Initialize Chrome stuff. |
| 926 base::AtExitManager exit_manager; |
| 927 base::EnableTerminationOnHeapCorruption(); |
| 928 app::RegisterPathProvider(); |
| 929 ResourceBundle::InitSharedInstance(L"en-US"); |
| 930 |
| 931 // Load libcros. |
| 932 chrome::RegisterPathProvider(); // for libcros.so. |
| 933 CHECK(chromeos::CrosLibrary::EnsureLoaded()) |
| 934 << "Failed to load libcros"; |
| 935 |
| 936 // Create the main message loop. |
| 937 MessageLoop main_message_loop(MessageLoop::TYPE_UI); |
| 938 |
| 939 // Create the candidate window controller. |
| 940 chromeos::CandidateWindowController controller; |
| 941 controller.Init(); |
| 942 |
| 943 // Start the main loop. |
| 944 views::AcceleratorHandler accelerator_handler; |
| 945 MessageLoopForUI::current()->Run(&accelerator_handler); |
| 946 |
| 947 return 0; |
| 948 } |
OLD | NEW |