Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/ui/gtk/omnibox/omnibox_popup_view_gtk.h" | 5 #include "chrome/browser/ui/gtk/omnibox/omnibox_popup_view_gtk.h" |
| 6 | 6 |
| 7 #include <gtk/gtk.h> | 7 #include <gtk/gtk.h> |
| 8 | 8 |
| 9 #include <algorithm> | 9 #include <algorithm> |
| 10 #include <string> | 10 #include <string> |
| (...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 90 // UTF-8 Left-to-right embedding. | 90 // UTF-8 Left-to-right embedding. |
| 91 const char* kLRE = "\xe2\x80\xaa"; | 91 const char* kLRE = "\xe2\x80\xaa"; |
| 92 | 92 |
| 93 // Return a Rect covering the whole area of |window|. | 93 // Return a Rect covering the whole area of |window|. |
| 94 gfx::Rect GetWindowRect(GdkWindow* window) { | 94 gfx::Rect GetWindowRect(GdkWindow* window) { |
| 95 gint width = gdk_window_get_width(window); | 95 gint width = gdk_window_get_width(window); |
| 96 gint height = gdk_window_get_height(window); | 96 gint height = gdk_window_get_height(window); |
| 97 return gfx::Rect(width, height); | 97 return gfx::Rect(width, height); |
| 98 } | 98 } |
| 99 | 99 |
| 100 // Return a Rect for the space for a result line. This excludes the border, | |
| 101 // but includes the padding. This is the area that is colored for a selection. | |
| 102 gfx::Rect GetRectForLine(size_t line, int width) { | |
| 103 return gfx::Rect(kBorderThickness, | |
| 104 (line * kHeightPerResult) + kBorderThickness, | |
| 105 width - (kBorderThickness * 2), | |
| 106 kHeightPerResult); | |
| 107 } | |
| 108 | |
| 109 // TODO(deanm): Find some better home for this, and make it more efficient. | 100 // TODO(deanm): Find some better home for this, and make it more efficient. |
| 110 size_t GetUTF8Offset(const string16& text, size_t text_offset) { | 101 size_t GetUTF8Offset(const string16& text, size_t text_offset) { |
| 111 return UTF16ToUTF8(text.substr(0, text_offset)).length(); | 102 return UTF16ToUTF8(text.substr(0, text_offset)).length(); |
| 112 } | 103 } |
| 113 | 104 |
| 114 // Generates the normal URL color, a green color used in unhighlighted URL | 105 // Generates the normal URL color, a green color used in unhighlighted URL |
| 115 // text. It is a mix of |kURLTextColor| and the current text color. Unlike the | 106 // text. It is a mix of |kURLTextColor| and the current text color. Unlike the |
| 116 // selected text color, it is more important to match the qualities of the | 107 // selected text color, it is more important to match the qualities of the |
| 117 // foreground typeface color instead of taking the background into account. | 108 // foreground typeface color instead of taking the background into account. |
| 118 GdkColor NormalURLColor(GdkColor foreground) { | 109 GdkColor NormalURLColor(GdkColor foreground) { |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 162 // The luminance should match the luminance of the foreground text. Again, | 153 // The luminance should match the luminance of the foreground text. Again, |
| 163 // we clamp so as to have at some amount of color (green) in the text. | 154 // we clamp so as to have at some amount of color (green) in the text. |
| 164 double opposite_l = fg_hsl.l; | 155 double opposite_l = fg_hsl.l; |
| 165 double l = std::max(0.1, std::min(0.9, opposite_l)); | 156 double l = std::max(0.1, std::min(0.9, opposite_l)); |
| 166 | 157 |
| 167 color_utils::HSL output = { hue_hsl.h, s, l }; | 158 color_utils::HSL output = { hue_hsl.h, s, l }; |
| 168 return gfx::SkColorToGdkColor(color_utils::HSLToSkColor(output, 255)); | 159 return gfx::SkColorToGdkColor(color_utils::HSLToSkColor(output, 255)); |
| 169 } | 160 } |
| 170 } // namespace | 161 } // namespace |
| 171 | 162 |
| 172 void OmniboxPopupViewGtk::SetupLayoutForMatch( | |
| 173 PangoLayout* layout, | |
| 174 const string16& text, | |
| 175 const AutocompleteMatch::ACMatchClassifications& classifications, | |
| 176 const GdkColor* base_color, | |
| 177 const GdkColor* dim_color, | |
| 178 const GdkColor* url_color, | |
| 179 const std::string& prefix_text) { | |
| 180 // In RTL, mark text with left-to-right embedding mark if there is no strong | |
| 181 // RTL characters inside it, so the ending punctuation displays correctly | |
| 182 // and the eliding ellipsis displays correctly. We only mark the text with | |
| 183 // LRE. Wrapping it with LRE and PDF by calling AdjustStringForLocaleDirection | |
| 184 // or WrapStringWithLTRFormatting will render the elllipsis at the left of the | |
| 185 // elided pure LTR text. | |
| 186 bool marked_with_lre = false; | |
| 187 string16 localized_text = text; | |
| 188 // Pango is really easy to overflow and send into a computational death | |
| 189 // spiral that can corrupt the screen. Assume that we'll never have more than | |
| 190 // 2000 characters, which should be a safe assumption until we all get robot | |
| 191 // eyes. http://crbug.com/66576 | |
| 192 if (localized_text.length() > 2000) | |
| 193 localized_text = localized_text.substr(0, 2000); | |
| 194 bool is_rtl = base::i18n::IsRTL(); | |
| 195 if (is_rtl && !base::i18n::StringContainsStrongRTLChars(localized_text)) { | |
| 196 localized_text.insert(0, 1, base::i18n::kLeftToRightEmbeddingMark); | |
| 197 marked_with_lre = true; | |
| 198 } | |
| 199 | |
| 200 // We can have a prefix, or insert additional characters while processing the | |
| 201 // classifications. We need to take this in to account when we translate the | |
| 202 // UTF-16 offsets in the classification into text_utf8 byte offsets. | |
| 203 size_t additional_offset = prefix_text.length(); // Length in utf-8 bytes. | |
| 204 std::string text_utf8 = prefix_text + UTF16ToUTF8(localized_text); | |
| 205 | |
| 206 PangoAttrList* attrs = pango_attr_list_new(); | |
| 207 | |
| 208 // TODO(deanm): This is a hack, just to handle coloring prefix_text. | |
| 209 // Hopefully I can clean up the match situation a bit and this will | |
| 210 // come out cleaner. For now, apply the base color to the whole text | |
| 211 // so that our prefix will have the base color applied. | |
| 212 PangoAttribute* base_fg_attr = pango_attr_foreground_new( | |
| 213 base_color->red, base_color->green, base_color->blue); | |
| 214 pango_attr_list_insert(attrs, base_fg_attr); // Ownership taken. | |
| 215 | |
| 216 // Walk through the classifications, they are linear, in order, and should | |
| 217 // cover the entire text. We create a bunch of overlapping attributes, | |
| 218 // extending from the offset to the end of the string. The ones created | |
| 219 // later will override the previous ones, meaning we will still setup each | |
| 220 // portion correctly, we just don't need to compute the end offset. | |
| 221 for (ACMatchClassifications::const_iterator i = classifications.begin(); | |
| 222 i != classifications.end(); ++i) { | |
| 223 size_t offset = GetUTF8Offset(localized_text, i->offset) + | |
| 224 additional_offset; | |
| 225 | |
| 226 // TODO(deanm): All the colors should probably blend based on whether this | |
| 227 // result is selected or not. This would include the green URLs. Right | |
| 228 // now the caller is left to blend only the base color. Do we need to | |
| 229 // handle things like DIM urls? Turns out DIM means something different | |
| 230 // than you'd think, all of the description text is not DIM, it is a | |
| 231 // special case that is not very common, but we should figure out and | |
| 232 // support it. | |
| 233 const GdkColor* color = base_color; | |
| 234 if (i->style & ACMatchClassification::URL) { | |
| 235 color = url_color; | |
| 236 // Insert a left to right embedding to make sure that URLs are shown LTR. | |
| 237 if (is_rtl && !marked_with_lre) { | |
| 238 std::string lre(kLRE); | |
| 239 text_utf8.insert(offset, lre); | |
| 240 additional_offset += lre.length(); | |
| 241 } | |
| 242 } | |
| 243 | |
| 244 if (i->style & ACMatchClassification::DIM) | |
| 245 color = dim_color; | |
| 246 | |
| 247 PangoAttribute* fg_attr = pango_attr_foreground_new( | |
| 248 color->red, color->green, color->blue); | |
| 249 fg_attr->start_index = offset; | |
| 250 pango_attr_list_insert(attrs, fg_attr); // Ownership taken. | |
| 251 | |
| 252 // Matched portions are bold, otherwise use the normal weight. | |
| 253 PangoWeight weight = (i->style & ACMatchClassification::MATCH) ? | |
| 254 PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL; | |
| 255 PangoAttribute* weight_attr = pango_attr_weight_new(weight); | |
| 256 weight_attr->start_index = offset; | |
| 257 pango_attr_list_insert(attrs, weight_attr); // Ownership taken. | |
| 258 } | |
| 259 | |
| 260 pango_layout_set_text(layout, text_utf8.data(), text_utf8.length()); | |
| 261 pango_layout_set_attributes(layout, attrs); // Ref taken. | |
| 262 pango_attr_list_unref(attrs); | |
| 263 } | |
| 264 | |
| 265 OmniboxPopupViewGtk::OmniboxPopupViewGtk(const gfx::Font& font, | 163 OmniboxPopupViewGtk::OmniboxPopupViewGtk(const gfx::Font& font, |
| 266 OmniboxView* omnibox_view, | 164 OmniboxView* omnibox_view, |
| 267 OmniboxEditModel* edit_model, | 165 OmniboxEditModel* edit_model, |
| 268 GtkWidget* location_bar) | 166 GtkWidget* location_bar) |
| 269 : model_(new OmniboxPopupModel(this, edit_model)), | 167 : omnibox_view_(omnibox_view), |
| 270 omnibox_view_(omnibox_view), | |
| 271 location_bar_(location_bar), | 168 location_bar_(location_bar), |
| 272 window_(gtk_window_new(GTK_WINDOW_POPUP)), | 169 window_(gtk_window_new(GTK_WINDOW_POPUP)), |
| 273 layout_(NULL), | 170 layout_(NULL), |
| 274 theme_service_(GtkThemeService::GetFrom(edit_model->profile())), | 171 theme_service_(NULL), |
| 275 font_(font.DeriveFont(kEditFontAdjust)), | 172 font_(font.DeriveFont(kEditFontAdjust)), |
| 276 ignore_mouse_drag_(false), | 173 ignore_mouse_drag_(false), |
| 277 opened_(false) { | 174 opened_(false) { |
| 175 // edit_model may be NULL in unit tests. | |
| 176 if (edit_model) { | |
| 177 model_.reset(new OmniboxPopupModel(this, edit_model)); | |
| 178 theme_service_ = GtkThemeService::GetFrom(edit_model->profile()); | |
| 179 } | |
| 180 } | |
| 181 | |
| 182 void OmniboxPopupViewGtk::Init() { | |
| 278 gtk_widget_set_can_focus(window_, FALSE); | 183 gtk_widget_set_can_focus(window_, FALSE); |
| 279 // Don't allow the window to be resized. This also forces the window to | 184 // Don't allow the window to be resized. This also forces the window to |
| 280 // shrink down to the size of its child contents. | 185 // shrink down to the size of its child contents. |
| 281 gtk_window_set_resizable(GTK_WINDOW(window_), FALSE); | 186 gtk_window_set_resizable(GTK_WINDOW(window_), FALSE); |
| 282 gtk_widget_set_app_paintable(window_, TRUE); | 187 gtk_widget_set_app_paintable(window_, TRUE); |
| 283 // Have GTK double buffer around the expose signal. | 188 // Have GTK double buffer around the expose signal. |
| 284 gtk_widget_set_double_buffered(window_, TRUE); | 189 gtk_widget_set_double_buffered(window_, TRUE); |
| 285 | 190 |
| 286 // Cache the layout so we don't have to create it for every expose. If we | 191 // Cache the layout so we don't have to create it for every expose. If we |
| 287 // were a real widget we should handle changing directions, but we're not | 192 // were a real widget we should handle changing directions, but we're not |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 321 // r25080 (the original patch that added rounded corners here) should | 226 // r25080 (the original patch that added rounded corners here) should |
| 322 // eventually be cherry picked once I know what's going | 227 // eventually be cherry picked once I know what's going |
| 323 // on. http://crbug.com/22015. | 228 // on. http://crbug.com/22015. |
| 324 } | 229 } |
| 325 | 230 |
| 326 OmniboxPopupViewGtk::~OmniboxPopupViewGtk() { | 231 OmniboxPopupViewGtk::~OmniboxPopupViewGtk() { |
| 327 // Explicitly destroy our model here, before we destroy our GTK widgets. | 232 // Explicitly destroy our model here, before we destroy our GTK widgets. |
| 328 // This is because the model destructor can call back into us, and we need | 233 // This is because the model destructor can call back into us, and we need |
| 329 // to make sure everything is still valid when it does. | 234 // to make sure everything is still valid when it does. |
| 330 model_.reset(); | 235 model_.reset(); |
| 331 g_object_unref(layout_); | 236 // layout_ may be NULL in unit tests. |
| 332 gtk_widget_destroy(window_); | 237 if (layout_) { |
| 238 g_object_unref(layout_); | |
| 239 gtk_widget_destroy(window_); | |
| 240 } | |
| 333 } | 241 } |
| 334 | 242 |
| 335 bool OmniboxPopupViewGtk::IsOpen() const { | 243 bool OmniboxPopupViewGtk::IsOpen() const { |
| 336 return opened_; | 244 return opened_; |
| 337 } | 245 } |
| 338 | 246 |
| 339 void OmniboxPopupViewGtk::InvalidateLine(size_t line) { | 247 void OmniboxPopupViewGtk::InvalidateLine(size_t line) { |
|
Evan Stade
2013/09/04 23:33:47
perhaps
DCHECK_LE(GetHiddenLineCount(), line);
Jered
2013/09/09 16:26:26
I added an if here to return early (this actually
| |
| 340 // TODO(deanm): Is it possible to use some constant for the width, instead | 248 // TODO(deanm): Is it possible to use some constant for the width, instead |
| 341 // of having to query the width of the window? | 249 // of having to query the width of the window? |
| 342 GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window_)); | 250 GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window_)); |
| 343 GdkRectangle line_rect = GetRectForLine( | 251 GdkRectangle line_rect = GetRectForLine( |
| 344 line, GetWindowRect(gdk_window).width()).ToGdkRectangle(); | 252 line, GetWindowRect(gdk_window).width()).ToGdkRectangle(); |
| 345 gdk_window_invalidate_rect(gdk_window, &line_rect, FALSE); | 253 gdk_window_invalidate_rect(gdk_window, &line_rect, FALSE); |
| 346 } | 254 } |
| 347 | 255 |
| 348 void OmniboxPopupViewGtk::UpdatePopupAppearance() { | 256 void OmniboxPopupViewGtk::UpdatePopupAppearance() { |
| 349 const AutocompleteResult& result = model_->result(); | 257 const AutocompleteResult& result = GetResult(); |
| 350 if (result.empty()) { | 258 const size_t hidden_matches = GetHiddenMatchCount(); |
| 259 if (result.size() <= hidden_matches) { | |
| 351 Hide(); | 260 Hide(); |
| 352 return; | 261 return; |
| 353 } | 262 } |
| 354 | 263 |
| 355 Show(result.size()); | 264 Show(result.size() - hidden_matches); |
| 356 gtk_widget_queue_draw(window_); | 265 gtk_widget_queue_draw(window_); |
| 357 } | 266 } |
| 358 | 267 |
| 359 gfx::Rect OmniboxPopupViewGtk::GetTargetBounds() { | 268 gfx::Rect OmniboxPopupViewGtk::GetTargetBounds() { |
| 360 if (!gtk_widget_get_realized(window_)) | 269 if (!gtk_widget_get_realized(window_)) |
| 361 return gfx::Rect(); | 270 return gfx::Rect(); |
| 362 | 271 |
| 363 gfx::Rect retval = ui::GetWidgetScreenBounds(window_); | 272 gfx::Rect retval = ui::GetWidgetScreenBounds(window_); |
| 364 | 273 |
| 365 // The widget bounds don't update synchronously so may be out of sync with | 274 // The widget bounds don't update synchronously so may be out of sync with |
| (...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 420 gtk_util::AverageColors(content_text_color_, | 329 gtk_util::AverageColors(content_text_color_, |
| 421 background_color_); | 330 background_color_); |
| 422 selected_content_dim_text_color_ = | 331 selected_content_dim_text_color_ = |
| 423 gtk_util::AverageColors(selected_content_text_color_, | 332 gtk_util::AverageColors(selected_content_text_color_, |
| 424 selected_background_color_); | 333 selected_background_color_); |
| 425 | 334 |
| 426 // Set the background color, so we don't need to paint it manually. | 335 // Set the background color, so we don't need to paint it manually. |
| 427 gtk_widget_modify_bg(window_, GTK_STATE_NORMAL, &background_color_); | 336 gtk_widget_modify_bg(window_, GTK_STATE_NORMAL, &background_color_); |
| 428 } | 337 } |
| 429 | 338 |
| 339 size_t OmniboxPopupViewGtk::LineFromY(int y) const { | |
| 340 DCHECK_NE(0U, model_->result().size()); | |
| 341 size_t line = std::max(y - kBorderThickness, 0) / kHeightPerResult; | |
| 342 return std::min(line + GetHiddenMatchCount(), GetResult().size() - 1); | |
| 343 } | |
| 344 | |
| 345 gfx::Rect OmniboxPopupViewGtk::GetRectForLine(size_t line, int width) const { | |
| 346 size_t visible_line = line - GetHiddenMatchCount(); | |
| 347 return gfx::Rect(kBorderThickness, | |
| 348 (visible_line * kHeightPerResult) + kBorderThickness, | |
| 349 width - (kBorderThickness * 2), | |
| 350 kHeightPerResult); | |
| 351 } | |
| 352 | |
| 353 size_t OmniboxPopupViewGtk::GetHiddenMatchCount() const { | |
| 354 return GetResult().ShouldHideTopMatch() ? 1 : 0; | |
| 355 } | |
| 356 | |
| 357 const AutocompleteResult& OmniboxPopupViewGtk::GetResult() const { | |
| 358 return model_->result(); | |
| 359 } | |
| 360 | |
| 361 // static | |
| 362 void OmniboxPopupViewGtk::SetupLayoutForMatch( | |
| 363 PangoLayout* layout, | |
| 364 const string16& text, | |
| 365 const AutocompleteMatch::ACMatchClassifications& classifications, | |
| 366 const GdkColor* base_color, | |
| 367 const GdkColor* dim_color, | |
| 368 const GdkColor* url_color, | |
| 369 const std::string& prefix_text) { | |
| 370 // In RTL, mark text with left-to-right embedding mark if there is no strong | |
| 371 // RTL characters inside it, so the ending punctuation displays correctly | |
| 372 // and the eliding ellipsis displays correctly. We only mark the text with | |
| 373 // LRE. Wrapping it with LRE and PDF by calling AdjustStringForLocaleDirection | |
| 374 // or WrapStringWithLTRFormatting will render the elllipsis at the left of the | |
| 375 // elided pure LTR text. | |
| 376 bool marked_with_lre = false; | |
| 377 string16 localized_text = text; | |
| 378 // Pango is really easy to overflow and send into a computational death | |
| 379 // spiral that can corrupt the screen. Assume that we'll never have more than | |
| 380 // 2000 characters, which should be a safe assumption until we all get robot | |
| 381 // eyes. http://crbug.com/66576 | |
| 382 if (localized_text.length() > 2000) | |
| 383 localized_text = localized_text.substr(0, 2000); | |
| 384 bool is_rtl = base::i18n::IsRTL(); | |
| 385 if (is_rtl && !base::i18n::StringContainsStrongRTLChars(localized_text)) { | |
| 386 localized_text.insert(0, 1, base::i18n::kLeftToRightEmbeddingMark); | |
| 387 marked_with_lre = true; | |
| 388 } | |
| 389 | |
| 390 // We can have a prefix, or insert additional characters while processing the | |
| 391 // classifications. We need to take this in to account when we translate the | |
| 392 // UTF-16 offsets in the classification into text_utf8 byte offsets. | |
| 393 size_t additional_offset = prefix_text.length(); // Length in utf-8 bytes. | |
| 394 std::string text_utf8 = prefix_text + UTF16ToUTF8(localized_text); | |
| 395 | |
| 396 PangoAttrList* attrs = pango_attr_list_new(); | |
| 397 | |
| 398 // TODO(deanm): This is a hack, just to handle coloring prefix_text. | |
| 399 // Hopefully I can clean up the match situation a bit and this will | |
| 400 // come out cleaner. For now, apply the base color to the whole text | |
| 401 // so that our prefix will have the base color applied. | |
| 402 PangoAttribute* base_fg_attr = pango_attr_foreground_new( | |
| 403 base_color->red, base_color->green, base_color->blue); | |
| 404 pango_attr_list_insert(attrs, base_fg_attr); // Ownership taken. | |
| 405 | |
| 406 // Walk through the classifications, they are linear, in order, and should | |
| 407 // cover the entire text. We create a bunch of overlapping attributes, | |
| 408 // extending from the offset to the end of the string. The ones created | |
| 409 // later will override the previous ones, meaning we will still setup each | |
| 410 // portion correctly, we just don't need to compute the end offset. | |
| 411 for (ACMatchClassifications::const_iterator i = classifications.begin(); | |
| 412 i != classifications.end(); ++i) { | |
| 413 size_t offset = GetUTF8Offset(localized_text, i->offset) + | |
| 414 additional_offset; | |
| 415 | |
| 416 // TODO(deanm): All the colors should probably blend based on whether this | |
| 417 // result is selected or not. This would include the green URLs. Right | |
| 418 // now the caller is left to blend only the base color. Do we need to | |
| 419 // handle things like DIM urls? Turns out DIM means something different | |
| 420 // than you'd think, all of the description text is not DIM, it is a | |
| 421 // special case that is not very common, but we should figure out and | |
| 422 // support it. | |
| 423 const GdkColor* color = base_color; | |
| 424 if (i->style & ACMatchClassification::URL) { | |
| 425 color = url_color; | |
| 426 // Insert a left to right embedding to make sure that URLs are shown LTR. | |
| 427 if (is_rtl && !marked_with_lre) { | |
| 428 std::string lre(kLRE); | |
| 429 text_utf8.insert(offset, lre); | |
| 430 additional_offset += lre.length(); | |
| 431 } | |
| 432 } | |
| 433 | |
| 434 if (i->style & ACMatchClassification::DIM) | |
| 435 color = dim_color; | |
| 436 | |
| 437 PangoAttribute* fg_attr = pango_attr_foreground_new( | |
| 438 color->red, color->green, color->blue); | |
| 439 fg_attr->start_index = offset; | |
| 440 pango_attr_list_insert(attrs, fg_attr); // Ownership taken. | |
| 441 | |
| 442 // Matched portions are bold, otherwise use the normal weight. | |
| 443 PangoWeight weight = (i->style & ACMatchClassification::MATCH) ? | |
| 444 PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL; | |
| 445 PangoAttribute* weight_attr = pango_attr_weight_new(weight); | |
| 446 weight_attr->start_index = offset; | |
| 447 pango_attr_list_insert(attrs, weight_attr); // Ownership taken. | |
| 448 } | |
| 449 | |
| 450 pango_layout_set_text(layout, text_utf8.data(), text_utf8.length()); | |
| 451 pango_layout_set_attributes(layout, attrs); // Ref taken. | |
| 452 pango_attr_list_unref(attrs); | |
| 453 } | |
| 454 | |
| 430 void OmniboxPopupViewGtk::Show(size_t num_results) { | 455 void OmniboxPopupViewGtk::Show(size_t num_results) { |
| 431 gint origin_x, origin_y; | 456 gint origin_x, origin_y; |
| 432 GdkWindow* gdk_window = gtk_widget_get_window(location_bar_); | 457 GdkWindow* gdk_window = gtk_widget_get_window(location_bar_); |
| 433 gdk_window_get_origin(gdk_window, &origin_x, &origin_y); | 458 gdk_window_get_origin(gdk_window, &origin_x, &origin_y); |
| 434 GtkAllocation allocation; | 459 GtkAllocation allocation; |
| 435 gtk_widget_get_allocation(location_bar_, &allocation); | 460 gtk_widget_get_allocation(location_bar_, &allocation); |
| 436 | 461 |
| 437 int horizontal_offset = 1; | 462 int horizontal_offset = 1; |
| 438 gtk_window_move(GTK_WINDOW(window_), | 463 gtk_window_move(GTK_WINDOW(window_), |
| 439 origin_x + allocation.x - kBorderThickness + horizontal_offset, | 464 origin_x + allocation.x - kBorderThickness + horizontal_offset, |
| (...skipping 13 matching lines...) Expand all Loading... | |
| 453 } | 478 } |
| 454 | 479 |
| 455 void OmniboxPopupViewGtk::StackWindow() { | 480 void OmniboxPopupViewGtk::StackWindow() { |
| 456 gfx::NativeView omnibox_view = omnibox_view_->GetNativeView(); | 481 gfx::NativeView omnibox_view = omnibox_view_->GetNativeView(); |
| 457 DCHECK(GTK_IS_WIDGET(omnibox_view)); | 482 DCHECK(GTK_IS_WIDGET(omnibox_view)); |
| 458 GtkWidget* toplevel = gtk_widget_get_toplevel(omnibox_view); | 483 GtkWidget* toplevel = gtk_widget_get_toplevel(omnibox_view); |
| 459 DCHECK(gtk_widget_is_toplevel(toplevel)); | 484 DCHECK(gtk_widget_is_toplevel(toplevel)); |
| 460 ui::StackPopupWindow(window_, toplevel); | 485 ui::StackPopupWindow(window_, toplevel); |
| 461 } | 486 } |
| 462 | 487 |
| 463 size_t OmniboxPopupViewGtk::LineFromY(int y) { | |
| 464 DCHECK_NE(0U, model_->result().size()); | |
| 465 size_t line = std::max(y - kBorderThickness, 0) / kHeightPerResult; | |
| 466 return std::min(line, model_->result().size() - 1); | |
| 467 } | |
| 468 | |
| 469 void OmniboxPopupViewGtk::AcceptLine(size_t line, | 488 void OmniboxPopupViewGtk::AcceptLine(size_t line, |
| 470 WindowOpenDisposition disposition) { | 489 WindowOpenDisposition disposition) { |
| 471 // OpenMatch() may close the popup, which will clear the result set and, by | 490 // OpenMatch() may close the popup, which will clear the result set and, by |
| 472 // extension, |match| and its contents. So copy the relevant match out to | 491 // extension, |match| and its contents. So copy the relevant match out to |
| 473 // make sure it stays alive until the call completes. | 492 // make sure it stays alive until the call completes. |
| 474 AutocompleteMatch match = model_->result().match_at(line); | 493 AutocompleteMatch match = GetResult().match_at(line); |
| 475 omnibox_view_->OpenMatch(match, disposition, GURL(), line); | 494 omnibox_view_->OpenMatch(match, disposition, GURL(), line); |
| 476 } | 495 } |
| 477 | 496 |
| 478 gfx::Image OmniboxPopupViewGtk::IconForMatch( | 497 gfx::Image OmniboxPopupViewGtk::IconForMatch( |
| 479 const AutocompleteMatch& match, | 498 const AutocompleteMatch& match, |
| 480 bool selected, | 499 bool selected, |
| 481 bool is_selected_keyword) { | 500 bool is_selected_keyword) { |
| 482 const gfx::Image image = model_->GetIconIfExtensionMatch(match); | 501 const gfx::Image image = model_->GetIconIfExtensionMatch(match); |
| 483 if (!image.IsEmpty()) | 502 if (!image.IsEmpty()) |
| 484 return image; | 503 return image; |
| (...skipping 29 matching lines...) Expand all Loading... | |
| 514 } | 533 } |
| 515 } | 534 } |
| 516 | 535 |
| 517 return theme_service_->GetImageNamed(icon); | 536 return theme_service_->GetImageNamed(icon); |
| 518 } | 537 } |
| 519 | 538 |
| 520 void OmniboxPopupViewGtk::GetVisibleMatchForInput( | 539 void OmniboxPopupViewGtk::GetVisibleMatchForInput( |
| 521 size_t index, | 540 size_t index, |
| 522 const AutocompleteMatch** match, | 541 const AutocompleteMatch** match, |
| 523 bool* is_selected_keyword) { | 542 bool* is_selected_keyword) { |
| 524 const AutocompleteResult& result = model_->result(); | 543 const AutocompleteResult& result = GetResult(); |
| 525 | 544 |
| 526 if (result.match_at(index).associated_keyword.get() && | 545 if (result.match_at(index).associated_keyword.get() && |
| 527 model_->selected_line() == index && | 546 model_->selected_line() == index && |
| 528 model_->selected_line_state() == OmniboxPopupModel::KEYWORD) { | 547 model_->selected_line_state() == OmniboxPopupModel::KEYWORD) { |
| 529 *match = result.match_at(index).associated_keyword.get(); | 548 *match = result.match_at(index).associated_keyword.get(); |
| 530 *is_selected_keyword = true; | 549 *is_selected_keyword = true; |
| 531 return; | 550 return; |
| 532 } | 551 } |
| 533 | 552 |
| 534 *match = &result.match_at(index); | 553 *match = &result.match_at(index); |
| (...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 587 default: | 606 default: |
| 588 // Don't open the result. | 607 // Don't open the result. |
| 589 break; | 608 break; |
| 590 } | 609 } |
| 591 return TRUE; | 610 return TRUE; |
| 592 } | 611 } |
| 593 | 612 |
| 594 gboolean OmniboxPopupViewGtk::HandleExpose(GtkWidget* widget, | 613 gboolean OmniboxPopupViewGtk::HandleExpose(GtkWidget* widget, |
| 595 GdkEventExpose* event) { | 614 GdkEventExpose* event) { |
| 596 bool ltr = !base::i18n::IsRTL(); | 615 bool ltr = !base::i18n::IsRTL(); |
| 597 const AutocompleteResult& result = model_->result(); | 616 const AutocompleteResult& result = GetResult(); |
| 598 | 617 |
| 599 gfx::Rect window_rect = GetWindowRect(event->window); | 618 gfx::Rect window_rect = GetWindowRect(event->window); |
| 600 gfx::Rect damage_rect = gfx::Rect(event->area); | 619 gfx::Rect damage_rect = gfx::Rect(event->area); |
| 601 // Handle when our window is super narrow. A bunch of the calculations | 620 // Handle when our window is super narrow. A bunch of the calculations |
| 602 // below would go negative, and really we're not going to fit anything | 621 // below would go negative, and really we're not going to fit anything |
| 603 // useful in such a small window anyway. Just don't paint anything. | 622 // useful in such a small window anyway. Just don't paint anything. |
| 604 // This means we won't draw the border, but, yeah, whatever. | 623 // This means we won't draw the border, but, yeah, whatever. |
| 605 // TODO(deanm): Make the code more robust and remove this check. | 624 // TODO(deanm): Make the code more robust and remove this check. |
| 606 if (window_rect.width() < (kIconAreaWidth * 3)) | 625 if (window_rect.width() < (kIconAreaWidth * 3)) |
| 607 return TRUE; | 626 return TRUE; |
| 608 | 627 |
| 609 cairo_t* cr = gdk_cairo_create(gtk_widget_get_window(widget)); | 628 cairo_t* cr = gdk_cairo_create(gtk_widget_get_window(widget)); |
| 610 gdk_cairo_rectangle(cr, &event->area); | 629 gdk_cairo_rectangle(cr, &event->area); |
| 611 cairo_clip(cr); | 630 cairo_clip(cr); |
| 612 | 631 |
| 613 // This assert is kinda ugly, but it would be more currently unneeded work | 632 // This assert is kinda ugly, but it would be more currently unneeded work |
| 614 // to support painting a border that isn't 1 pixel thick. There is no point | 633 // to support painting a border that isn't 1 pixel thick. There is no point |
| 615 // in writing that code now, and explode if that day ever comes. | 634 // in writing that code now, and explode if that day ever comes. |
| 616 COMPILE_ASSERT(kBorderThickness == 1, border_1px_implied); | 635 COMPILE_ASSERT(kBorderThickness == 1, border_1px_implied); |
| 617 // Draw the 1px border around the entire window. | 636 // Draw the 1px border around the entire window. |
| 618 gdk_cairo_set_source_color(cr, &border_color_); | 637 gdk_cairo_set_source_color(cr, &border_color_); |
| 619 cairo_rectangle(cr, 0, 0, window_rect.width(), window_rect.height()); | 638 cairo_rectangle(cr, 0, 0, window_rect.width(), window_rect.height()); |
| 620 cairo_stroke(cr); | 639 cairo_stroke(cr); |
| 621 | 640 |
| 622 pango_layout_set_height(layout_, kHeightPerResult * PANGO_SCALE); | 641 pango_layout_set_height(layout_, kHeightPerResult * PANGO_SCALE); |
| 623 | 642 |
| 624 for (size_t i = 0; i < result.size(); ++i) { | 643 for (size_t i = GetHiddenMatchCount(); i < result.size(); ++i) { |
| 625 gfx::Rect line_rect = GetRectForLine(i, window_rect.width()); | 644 gfx::Rect line_rect = GetRectForLine(i, window_rect.width()); |
| 626 // Only repaint and layout damaged lines. | 645 // Only repaint and layout damaged lines. |
| 627 if (!line_rect.Intersects(damage_rect)) | 646 if (!line_rect.Intersects(damage_rect)) |
| 628 continue; | 647 continue; |
| 629 | 648 |
| 630 const AutocompleteMatch* match = NULL; | 649 const AutocompleteMatch* match = NULL; |
| 631 bool is_selected_keyword = false; | 650 bool is_selected_keyword = false; |
| 632 GetVisibleMatchForInput(i, &match, &is_selected_keyword); | 651 GetVisibleMatchForInput(i, &match, &is_selected_keyword); |
| 633 bool is_selected = (model_->selected_line() == i); | 652 bool is_selected = (model_->selected_line() == i); |
| 634 bool is_hovered = (model_->hovered_line() == i); | 653 bool is_hovered = (model_->hovered_line() == i); |
| (...skipping 90 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 725 theme_service_->GetImageNamed( | 744 theme_service_->GetImageNamed( |
| 726 is_selected ? IDR_OMNIBOX_TTS_DARK : | 745 is_selected ? IDR_OMNIBOX_TTS_DARK : |
| 727 IDR_OMNIBOX_TTS), | 746 IDR_OMNIBOX_TTS), |
| 728 icon_start_x, line_rect.y() + kIconTopPadding); | 747 icon_start_x, line_rect.y() + kIconTopPadding); |
| 729 } | 748 } |
| 730 } | 749 } |
| 731 | 750 |
| 732 cairo_destroy(cr); | 751 cairo_destroy(cr); |
| 733 return TRUE; | 752 return TRUE; |
| 734 } | 753 } |
| OLD | NEW |