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
3 // found in the LICENSE file.
5 #include "chrome/browser/ui/gtk/avatar_menu_item_gtk.h"
7 #include <gdk/gdkkeysyms.h>
10 #include "base/message_loop/message_loop.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "chrome/browser/chrome_notification_types.h"
13 #include "chrome/browser/ui/gtk/gtk_chrome_link_button.h"
14 #include "chrome/browser/ui/gtk/gtk_theme_service.h"
15 #include "chrome/browser/ui/gtk/gtk_util.h"
16 #include "content/public/browser/notification_source.h"
17 #include "grit/generated_resources.h"
18 #include "grit/theme_resources.h"
19 #include "third_party/skia/include/core/SkBitmap.h"
20 #include "ui/base/gtk/gtk_hig_constants.h"
21 #include "ui/base/l10n/l10n_util.h"
22 #include "ui/base/resource/resource_bundle.h"
23 #include "ui/gfx/canvas.h"
24 #include "ui/gfx/gtk_util.h"
25 #include "ui/gfx/image/image.h"
26 #include "ui/gfx/text_elider.h"
30 // A checkmark is drawn in the lower-right corner of the active avatar image.
31 // This value is the x offset, in pixels, from that position.
32 const int kCheckMarkXOffset
= 2;
34 // The maximum width of a user name in pixels. Anything longer than this will be
36 const int kUserNameMaxWidth
= 200;
38 // The color of the item highlight when we're in chrome-theme mode.
39 const GdkColor kHighlightColor
= GDK_COLOR_RGB(0xe3, 0xed, 0xf6);
41 // The color of the background when we're in chrome-theme mode.
42 const GdkColor kBackgroundColor
= GDK_COLOR_RGB(0xff, 0xff, 0xff);
46 AvatarMenuItemGtk::AvatarMenuItemGtk(Delegate
* delegate
,
47 const AvatarMenu::Item
& item
,
49 GtkThemeService
* theme_service
)
50 : delegate_(delegate
),
52 item_index_(item_index
),
53 theme_service_(theme_service
),
55 link_alignment_(NULL
),
56 edit_profile_link_(NULL
) {
59 registrar_
.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED
,
60 content::Source
<ThemeService
>(theme_service_
));
61 theme_service_
->InitThemesFor(this);
64 AvatarMenuItemGtk::~AvatarMenuItemGtk() {
68 gboolean
AvatarMenuItemGtk::OnProfileClick(GtkWidget
* widget
,
69 GdkEventButton
* event
) {
70 delegate_
->OpenProfile(item_index_
);
74 gboolean
AvatarMenuItemGtk::OnProfileKeyPress(GtkWidget
* widget
,
76 if (event
->keyval
== GDK_Return
||
77 event
->keyval
== GDK_ISO_Enter
||
78 event
->keyval
== GDK_KP_Enter
) {
80 delegate_
->EditProfile(item_index_
);
82 delegate_
->OpenProfile(item_index_
);
88 void AvatarMenuItemGtk::ShowStatusLabel() {
89 gtk_widget_show(status_label_
);
90 gtk_widget_hide(link_alignment_
);
93 void AvatarMenuItemGtk::ShowEditLink() {
94 gtk_widget_hide(status_label_
);
95 gtk_widget_show(link_alignment_
);
98 gboolean
AvatarMenuItemGtk::OnProfileFocusIn(GtkWidget
* widget
,
99 GdkEventFocus
* event
) {
106 gboolean
AvatarMenuItemGtk::OnProfileFocusOut(GtkWidget
* widget
,
107 GdkEventFocus
* event
) {
114 gboolean
AvatarMenuItemGtk::OnProfileEnter(GtkWidget
* widget
,
115 GdkEventCrossing
* event
) {
116 if (event
->detail
== GDK_NOTIFY_INFERIOR
)
119 gtk_widget_modify_bg(widget
, GTK_STATE_NORMAL
, &highlighted_color_
);
126 gboolean
AvatarMenuItemGtk::OnProfileLeave(GtkWidget
* widget
,
127 GdkEventCrossing
* event
) {
128 if (event
->detail
== GDK_NOTIFY_INFERIOR
)
131 gtk_widget_modify_bg(widget
, GTK_STATE_NORMAL
, unhighlighted_color_
);
138 void AvatarMenuItemGtk::Observe(int type
,
139 const content::NotificationSource
& source
,
140 const content::NotificationDetails
& details
) {
141 DCHECK_EQ(type
, chrome::NOTIFICATION_BROWSER_THEME_CHANGED
);
142 bool using_native
= theme_service_
->UsingNativeTheme();
145 GtkStyle
* style
= gtk_rc_get_style(widget_
.get());
146 highlighted_color_
= style
->bg
[GTK_STATE_SELECTED
];
147 unhighlighted_color_
= NULL
;
149 highlighted_color_
= kHighlightColor
;
150 unhighlighted_color_
= &kBackgroundColor
;
153 // Assume that the widget isn't highlighted since theme changes will almost
154 // never happen while we're up.
155 gtk_widget_modify_bg(widget_
.get(), GTK_STATE_NORMAL
, unhighlighted_color_
);
158 void AvatarMenuItemGtk::OnEditProfileLinkClicked(GtkWidget
* link
) {
159 delegate_
->EditProfile(item_index_
);
162 gboolean
AvatarMenuItemGtk::OnEventBoxExpose(GtkWidget
* widget
,
163 GdkEventExpose
* event
) {
164 // Draw the focus rectangle.
165 if (gtk_widget_has_focus(widget
)) {
166 GtkAllocation allocation
;
167 gtk_widget_get_allocation(widget
, &allocation
);
168 gtk_paint_focus(gtk_widget_get_style(widget
),
169 gtk_widget_get_window(widget
),
170 gtk_widget_get_state(widget
),
171 &event
->area
, widget
, NULL
,
173 allocation
.width
, allocation
.height
);
179 void AvatarMenuItemGtk::Init(GtkThemeService
* theme_service
) {
180 widget_
.Own(gtk_event_box_new());
182 g_signal_connect(widget_
.get(), "button-press-event",
183 G_CALLBACK(OnProfileClickThunk
), this);
184 g_signal_connect(widget_
.get(), "enter-notify-event",
185 G_CALLBACK(OnProfileEnterThunk
), this);
186 g_signal_connect(widget_
.get(), "leave-notify-event",
187 G_CALLBACK(OnProfileLeaveThunk
), this);
188 g_signal_connect(widget_
.get(), "focus-in-event",
189 G_CALLBACK(OnProfileFocusInThunk
), this);
190 g_signal_connect(widget_
.get(), "focus-out-event",
191 G_CALLBACK(OnProfileFocusOutThunk
), this);
192 g_signal_connect(widget_
.get(), "key-press-event",
193 G_CALLBACK(OnProfileKeyPressThunk
), this);
194 g_signal_connect_after(widget_
.get(), "expose-event",
195 G_CALLBACK(OnEventBoxExposeThunk
), this);
197 GtkWidget
* item_hbox
= gtk_hbox_new(FALSE
, ui::kControlSpacing
);
198 GdkPixbuf
* avatar_pixbuf
= NULL
;
200 // If this profile is active then draw a check mark on the bottom right
201 // of the profile icon.
203 const SkBitmap
* avatar_image
= item_
.icon
.ToSkBitmap();
204 gfx::ImageSkiaRep avatar_image_rep
= gfx::ImageSkiaRep(*avatar_image
, 1.0f
);
205 gfx::Canvas
canvas(avatar_image_rep
, /* is_opaque */ true);
207 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
208 const gfx::ImageSkia
* check_image
= rb
.GetImageNamed(
209 IDR_PROFILE_SELECTED
).ToImageSkia();
210 gfx::Rect
check_rect(0, 0, check_image
->width(), check_image
->height());
211 int y
= avatar_image
->height() - check_image
->height();
212 int x
= avatar_image
->width() - check_image
->width() + kCheckMarkXOffset
;
213 canvas
.DrawImageInt(*check_image
, x
, y
);
215 SkBitmap final_image
= canvas
.ExtractImageRep().sk_bitmap();
216 avatar_pixbuf
= gfx::GdkPixbufFromSkBitmap(final_image
);
218 avatar_pixbuf
= gfx::GdkPixbufFromSkBitmap(*item_
.icon
.ToSkBitmap());
221 GtkWidget
* avatar_image
= gtk_image_new_from_pixbuf(avatar_pixbuf
);
222 g_object_unref(avatar_pixbuf
);
223 gtk_misc_set_alignment(GTK_MISC(avatar_image
), 0, 0);
224 gtk_box_pack_start(GTK_BOX(item_hbox
), avatar_image
, FALSE
, FALSE
, 0);
226 // The user name label.
227 GtkWidget
* item_vbox
= gtk_vbox_new(FALSE
, 0);
228 GtkWidget
* name_label
= NULL
;
229 base::string16 elided_name
= gfx::ElideText(item_
.name
,
234 name_label
= theme_service
->BuildLabel(base::UTF16ToUTF8(elided_name
),
237 char* markup
= g_markup_printf_escaped(
238 "<span weight='bold'>%s</span>",
239 base::UTF16ToUTF8(elided_name
).c_str());
240 gtk_label_set_markup(GTK_LABEL(name_label
), markup
);
244 gtk_misc_set_alignment(GTK_MISC(name_label
), 0, 0);
245 gtk_box_pack_start(GTK_BOX(item_vbox
), name_label
, TRUE
, TRUE
, 0);
247 // The sync status label.
248 status_label_
= theme_service_
->BuildLabel(std::string(), ui::kGdkBlack
);
249 char* markup
= g_markup_printf_escaped(
250 "<span size='small'>%s</span>",
251 base::UTF16ToUTF8(item_
.sync_state
).c_str());
252 gtk_label_set_markup(GTK_LABEL(status_label_
), markup
);
254 gtk_misc_set_alignment(GTK_MISC(status_label_
), 0, 0);
255 gtk_widget_set_no_show_all(status_label_
, TRUE
);
256 gtk_box_pack_start(GTK_BOX(item_vbox
), status_label_
, FALSE
, FALSE
, 0);
257 gtk_widget_show(status_label_
);
260 // The "edit your profile" link.
261 edit_profile_link_
= theme_service_
->BuildChromeLinkButton(
262 l10n_util::GetStringUTF8(IDS_PROFILES_EDIT_PROFILE_LINK
));
263 // Fix for bug#107348. edit link steals focus from menu item which
264 // hides edit link button in focus-out-event handler,
265 // so, it misses the click event.
266 gtk_widget_set_can_focus(edit_profile_link_
, FALSE
);
268 link_alignment_
= gtk_alignment_new(0, 0, 0, 0);
269 gtk_container_add(GTK_CONTAINER(link_alignment_
), edit_profile_link_
);
271 // The chrome link button contains a label that won't be shown if the button
272 // is set to "no show all", so show all first.
273 gtk_widget_show_all(link_alignment_
);
274 gtk_widget_set_no_show_all(link_alignment_
, TRUE
);
275 gtk_widget_hide(link_alignment_
);
277 gtk_box_pack_start(GTK_BOX(item_vbox
), link_alignment_
, FALSE
, FALSE
, 0);
279 g_signal_connect(edit_profile_link_
, "clicked",
280 G_CALLBACK(OnEditProfileLinkClickedThunk
), this);
282 GtkSizeGroup
* size_group
= gtk_size_group_new(GTK_SIZE_GROUP_BOTH
);
283 gtk_size_group_add_widget(size_group
, status_label_
);
284 gtk_size_group_add_widget(size_group
, link_alignment_
);
285 g_object_unref(size_group
);
288 gtk_box_pack_start(GTK_BOX(item_hbox
), item_vbox
, TRUE
, TRUE
, 0);
289 gtk_container_add(GTK_CONTAINER(widget_
.get()), item_hbox
);