diff options
-rwxr-xr-x | chrome/browser/autocomplete/autocomplete_popup_view_gtk.cc | 204 | ||||
-rwxr-xr-x | chrome/browser/autocomplete/autocomplete_popup_view_gtk.h | 15 |
2 files changed, 168 insertions, 51 deletions
diff --git a/chrome/browser/autocomplete/autocomplete_popup_view_gtk.cc b/chrome/browser/autocomplete/autocomplete_popup_view_gtk.cc index 94a6343..a93e9ab 100755 --- a/chrome/browser/autocomplete/autocomplete_popup_view_gtk.cc +++ b/chrome/browser/autocomplete/autocomplete_popup_view_gtk.cc @@ -4,6 +4,9 @@ #include "chrome/browser/autocomplete/autocomplete_popup_view_gtk.h" +#include <gtk/gtk.h> + +#include "base/basictypes.h" #include "base/gfx/gtk_util.h" #include "base/logging.h" #include "base/string_util.h" @@ -14,13 +17,67 @@ #include "chrome/browser/profile.h" #include "chrome/browser/search_engines/template_url.h" #include "chrome/browser/search_engines/template_url_model.h" +#include "chrome/common/gfx/chrome_font.h" #include "chrome/common/notification_service.h" namespace { -const GdkColor kPopupBorderColor = GDK_COLOR_RGB(0xc7, 0xca, 0xce); -const GdkColor kPopupBackground = GDK_COLOR_RGB(0xff, 0xff, 0xff); -const GdkColor kHighlightColor = GDK_COLOR_RGB(0xc1, 0xc8, 0xd9); +const GdkColor kBorderColor = GDK_COLOR_RGB(0xc7, 0xca, 0xce); +const GdkColor kBackgroundColor = GDK_COLOR_RGB(0xff, 0xff, 0xff); +const GdkColor kSelectedBackgroundColor = GDK_COLOR_RGB(0xdf, 0xe6, 0xf6); + +const int kBorderThickness = 1; +const int kHeightPerResult = 20; +// Additional distance below the edit control. +const int kTopMargin = 3; +// Space between edge and the text. This includes the border, so the text +// will be kLeftRightPadding - kBorderThickness away from the border. +const int kLeftRightPadding = 3; + +// TODO(deanm): We should put this on ChromeFont so it can be shared. +// Returns a new pango font, free with pango_font_description_free(). +PangoFontDescription* PangoFontFromChromeFont(const ChromeFont& chrome_font) { + ChromeFont font = chrome_font; // Copy so we can call non-const methods. + PangoFontDescription* pfd = pango_font_description_new(); + pango_font_description_set_family(pfd, WideToUTF8(font.FontName()).c_str()); + pango_font_description_set_size(pfd, font.FontSize() * PANGO_SCALE); + + switch (font.style()) { + case ChromeFont::NORMAL: + // Nothing to do, should already be PANGO_STYLE_NORMAL. + break; + case ChromeFont::BOLD: + pango_font_description_set_weight(pfd, PANGO_WEIGHT_BOLD); + break; + case ChromeFont::ITALIC: + pango_font_description_set_style(pfd, PANGO_STYLE_ITALIC); + break; + case ChromeFont::UNDERLINED: + // TODO(deanm): How to do underlined? Where do we use it? Probably have + // to paint it ourselves, see pango_font_metrics_get_underline_position. + break; + } + + return pfd; +} + +// Return a GdkRectangle covering the whole area of |window|. +GdkRectangle GetWindowRect(GdkWindow* window) { + gint width, height; + gdk_drawable_get_size(GDK_DRAWABLE(window), &width, &height); + GdkRectangle rect = {0, 0, width, height}; + return rect; +} + +// Return a rectangle for the space for a result. This excludes the border, +// but includes the padding. This is the area that is colored for a selection. +GdkRectangle GetRectForLine(size_t line, int width) { + GdkRectangle rect = {kBorderThickness, + (line * kHeightPerResult) + kBorderThickness, + width - (kBorderThickness * 2), + kHeightPerResult}; + return rect; +} } // namespace @@ -28,18 +85,29 @@ AutocompletePopupViewGtk::AutocompletePopupViewGtk( AutocompleteEditViewGtk* edit_view, AutocompleteEditModel* edit_model, Profile* profile) - : model_(new AutocompletePopupModel(this, edit_model, profile)), + : font_(NULL), + model_(new AutocompletePopupModel(this, edit_model, profile)), edit_view_(edit_view), window_(gtk_window_new(GTK_WINDOW_POPUP)), - vbox_(NULL), opened_(false) { GTK_WIDGET_UNSET_FLAGS(window_, GTK_CAN_FOCUS); // Don't allow the window to be resized. This also forces the window to // shrink down to the size of its child contents. gtk_window_set_resizable(GTK_WINDOW(window_), FALSE); - // Set up a 1 pixel border around the popup. - gtk_container_set_border_width(GTK_CONTAINER(window_), 1); - gtk_widget_modify_bg(window_, GTK_STATE_NORMAL, &kPopupBorderColor); + gtk_widget_set_app_paintable(window_, TRUE); + // Have GTK double buffer around the expose signal. + gtk_widget_set_double_buffered(window_, TRUE); + // Set the background color, so we don't need to paint it manually. + gtk_widget_modify_bg(window_, GTK_STATE_NORMAL, &kBackgroundColor); + + // TODO(deanm): We might want to eventually follow what Windows does and + // plumb a ChromeFont through. This is because popup windows have a + // different font size, although we could just derive that font here. + ChromeFont default_font; + font_ = PangoFontFromChromeFont(default_font); + + g_signal_connect(window_, "expose-event", + G_CALLBACK(&HandleExposeThunk), this); } AutocompletePopupViewGtk::~AutocompletePopupViewGtk() { @@ -47,13 +115,14 @@ AutocompletePopupViewGtk::~AutocompletePopupViewGtk() { // This is because the model destructor can call back into us, and we need // to make sure everything is still valid when it does. model_.reset(); - if (vbox_) - gtk_widget_destroy(vbox_); gtk_widget_destroy(window_); + pango_font_description_free(font_); } void AutocompletePopupViewGtk::InvalidateLine(size_t line) { - UpdatePopupAppearance(); + GdkRectangle rect = GetWindowRect(window_->window); + rect = GetRectForLine(line, rect.width); + gdk_window_invalidate_rect(window_->window, &rect, FALSE); } void AutocompletePopupViewGtk::UpdatePopupAppearance() { @@ -63,39 +132,8 @@ void AutocompletePopupViewGtk::UpdatePopupAppearance() { return; } - // TODO(deanm): This could be done better, but the code is temporary - // and will need to be replaced with custom drawing and not widgets. - if (vbox_) - gtk_widget_destroy(vbox_); - - vbox_ = gtk_vbox_new(FALSE, 0); - - for (size_t i = 0; i < result.size(); ++i) { - GtkWidget* label = gtk_label_new(NULL); - char* markup = g_markup_printf_escaped( - "%s <span weight=\"light\" size=\"small\">%s</span>", - WideToUTF8(result.match_at(i).contents).c_str(), - WideToUTF8(result.match_at(i).description).c_str()); - gtk_label_set_markup(GTK_LABEL(label), markup); - g_free (markup); - - // We need to put the labels in an event box for background painting. - GtkWidget* ebox = gtk_event_box_new(); - gtk_misc_set_alignment(GTK_MISC(label), 0, 0); - gtk_container_add(GTK_CONTAINER(ebox), label); - gtk_box_pack_start(GTK_BOX(vbox_), ebox, FALSE, FALSE, 0); - gtk_widget_show(label); - gtk_widget_show(ebox); - if (i == model_->selected_line()) { - gtk_widget_modify_bg(ebox, GTK_STATE_NORMAL, &kHighlightColor); - } else { - gtk_widget_modify_bg(ebox, GTK_STATE_NORMAL, &kPopupBackground); - } - } - - gtk_widget_show(vbox_); - gtk_container_add(GTK_CONTAINER(window_), vbox_); - Show(); + Show(result.size()); + gtk_widget_queue_draw(window_); } void AutocompletePopupViewGtk::OnHoverEnabledOrDisabled(bool disabled) { @@ -103,14 +141,16 @@ void AutocompletePopupViewGtk::OnHoverEnabledOrDisabled(bool disabled) { } void AutocompletePopupViewGtk::PaintUpdatesNow() { - UpdatePopupAppearance(); + // Paint our queued invalidations now, synchronously. + gdk_window_process_updates(window_->window, FALSE); } -void AutocompletePopupViewGtk::Show() { +void AutocompletePopupViewGtk::Show(size_t num_results) { gint x, y, width; edit_view_->BottomLeftPosWidth(&x, &y, &width); - gtk_window_move(GTK_WINDOW(window_), x, y); - gtk_widget_set_size_request(window_, width, -1); + gtk_window_move(GTK_WINDOW(window_), x, y + kTopMargin); + gtk_widget_set_size_request(window_, width, + (num_results * kHeightPerResult) + (kBorderThickness * 2)); gtk_widget_show(window_); opened_ = true; } @@ -119,3 +159,71 @@ void AutocompletePopupViewGtk::Hide() { gtk_widget_hide(window_); opened_ = false; } + +gboolean AutocompletePopupViewGtk::HandleExpose(GtkWidget* widget, + GdkEventExpose* event) { + const AutocompleteResult& result = model_->result(); + + GdkRectangle window_rect = GetWindowRect(event->window); + // Handle when our window is super narrow. A bunch of the calculations + // below would go negative, and really we're not going to fit anything + // useful in such a small window anyway. Just don't paint anything. + // This means we won't draw the border, but, yeah, whatever. + if (window_rect.width < (kLeftRightPadding * 3)) + return TRUE; + + GdkDrawable* drawable = GDK_DRAWABLE(event->window); + GdkGC* gc = gdk_gc_new(drawable); + + GdkGCValues default_gc_values; + gdk_gc_get_values(gc, &default_gc_values); + + // kBorderColor is unallocated, so use the GdkRGB routine. + gdk_gc_set_rgb_fg_color(gc, &kBorderColor); + + // This assert is kinda ugly, but it would be more currently unneeded work + // to support painting a border that isn't 1 pixel thick. There is no point + // in writing that code now, and explode if that day ever comes. + COMPILE_ASSERT(kBorderThickness == 1, border_1px_implied); + // Draw the 1px border around the entire window. + gdk_draw_rectangle(drawable, gc, FALSE, + 0, 0, + window_rect.width - 1, window_rect.height - 1); + + // Draw the background for the selected result line. + size_t selected = model_->selected_line(); + if (selected != AutocompletePopupModel::kNoMatch) { + gdk_gc_set_rgb_fg_color(gc, &kSelectedBackgroundColor); + GdkRectangle rect = GetRectForLine(selected, window_rect.width); + gdk_draw_rectangle(drawable, gc, TRUE, + rect.x, rect.y, rect.width, rect.height); + } + + // Restore the original GC foreground color. + gdk_gc_set_foreground(gc, &default_gc_values.foreground); + + // TODO(deanm): Cache the layout? How expensive is it to create? + PangoLayout* layout = gtk_widget_create_pango_layout(window_, NULL); + + pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END); + pango_layout_set_width(layout, + (window_rect.width - (kLeftRightPadding * 2)) * PANGO_SCALE); + pango_layout_set_height(layout, kHeightPerResult * PANGO_SCALE); + pango_layout_set_font_description(layout, font_); + + // TODO(deanm): Intersect the line and damage rects, and only repaint and + // layout the lines that are actually damaged. For now paint everything. + for (size_t i = 0; i < result.size(); ++i) { + std::string contents_utf8 = WideToUTF8(result.match_at(i).contents); + pango_layout_set_text(layout, contents_utf8.data(), contents_utf8.size()); + // TODO(deanm): Should probably try harder to vertically align the text. + gdk_draw_layout(drawable, gc, + kLeftRightPadding, i * kHeightPerResult + 1, + layout); + // TODO(deanm): Draw the "description" on the right of the contents. + } + + g_object_unref(gc); + + return TRUE; +} diff --git a/chrome/browser/autocomplete/autocomplete_popup_view_gtk.h b/chrome/browser/autocomplete/autocomplete_popup_view_gtk.h index 3c4fcc8..c1bd5ef 100755 --- a/chrome/browser/autocomplete/autocomplete_popup_view_gtk.h +++ b/chrome/browser/autocomplete/autocomplete_popup_view_gtk.h @@ -15,9 +15,9 @@ #include "chrome/browser/autocomplete/autocomplete_popup_view.h" #include "webkit/glue/window_open_disposition.h" -class AutocompletePopupModel; class AutocompleteEditModel; class AutocompleteEditViewGtk; +class AutocompletePopupModel; class Profile; class SkBitmap; @@ -38,14 +38,23 @@ class AutocompletePopupViewGtk : public AutocompletePopupView { AutocompletePopupModel* model() { return model_.get(); } private: - void Show(); + void Show(size_t num_results); void Hide(); + static gboolean HandleExposeThunk(GtkWidget* widget, GdkEventExpose* event, + gpointer userdata) { + return reinterpret_cast<AutocompletePopupViewGtk*>(userdata)-> + HandleExpose(widget, event); + } + + gboolean HandleExpose(GtkWidget* widget, GdkEventExpose* event); + + PangoFontDescription* font_; + scoped_ptr<AutocompletePopupModel> model_; AutocompleteEditViewGtk* edit_view_; GtkWidget* window_; - GtkWidget* vbox_; bool opened_; |