1// Copyright (c) 2011 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#include "chrome/browser/chromeos/frame/panel_controller.h" 6 7#include <vector> 8 9#include "base/logging.h" 10#include "base/memory/scoped_ptr.h" 11#include "base/memory/singleton.h" 12#include "base/string_util.h" 13#include "base/time.h" 14#include "base/utf_string_conversions.h" 15#include "chrome/browser/chromeos/wm_ipc.h" 16#include "content/common/notification_service.h" 17#include "grit/app_resources.h" 18#include "grit/generated_resources.h" 19#include "grit/theme_resources.h" 20#include "third_party/cros/chromeos_wm_ipc_enums.h" 21#include "third_party/skia/include/effects/SkBlurMaskFilter.h" 22#include "third_party/skia/include/effects/SkGradientShader.h" 23#include "ui/base/resource/resource_bundle.h" 24#include "ui/gfx/canvas_skia.h" 25#include "views/controls/button/image_button.h" 26#include "views/controls/image_view.h" 27#include "views/controls/label.h" 28#include "views/events/event.h" 29#include "views/painter.h" 30#include "views/view.h" 31#include "views/widget/widget.h" 32#include "views/window/window.h" 33 34namespace chromeos { 35 36static int close_button_width; 37static int close_button_height; 38static SkBitmap* close_button_n; 39static SkBitmap* close_button_m; 40static SkBitmap* close_button_h; 41static SkBitmap* close_button_p; 42static gfx::Font* active_font = NULL; 43static gfx::Font* inactive_font = NULL; 44 45namespace { 46 47const int kTitleHeight = 24; 48const int kTitleIconSize = 16; 49const int kTitleWidthPad = 4; 50const int kTitleHeightPad = 4; 51const int kTitleCornerRadius = 4; 52const int kTitleCloseButtonPad = 6; 53const SkColor kTitleActiveGradientStart = SK_ColorWHITE; 54const SkColor kTitleActiveGradientEnd = 0xffe7edf1; 55const SkColor kTitleUrgentGradientStart = 0xfffea044; 56const SkColor kTitleUrgentGradientEnd = 0xfffa983a; 57const SkColor kTitleActiveColor = SK_ColorBLACK; 58const SkColor kTitleInactiveColor = SK_ColorBLACK; 59const SkColor kTitleCloseButtonColor = SK_ColorBLACK; 60// Delay before the urgency can be set after it has been cleared. 61const base::TimeDelta kSetUrgentDelay = base::TimeDelta::FromMilliseconds(500); 62 63// Used to draw the background of the panel title window. 64class TitleBackgroundPainter : public views::Painter { 65 public: 66 explicit TitleBackgroundPainter(PanelController* controller) 67 : panel_controller_(controller) { } 68 69 private: 70 virtual void Paint(int w, int h, gfx::Canvas* canvas) { 71 SkRect rect = {0, 0, w, h}; 72 SkPath path; 73 SkScalar corners[] = { 74 kTitleCornerRadius, kTitleCornerRadius, 75 kTitleCornerRadius, kTitleCornerRadius, 76 0, 0, 77 0, 0 78 }; 79 path.addRoundRect(rect, corners); 80 SkPaint paint; 81 paint.setStyle(SkPaint::kFill_Style); 82 paint.setFlags(SkPaint::kAntiAlias_Flag); 83 SkPoint p[2] = { {0, 0}, {0, h} }; 84 SkColor colors[2] = {kTitleActiveGradientStart, kTitleActiveGradientEnd}; 85 if (panel_controller_->urgent()) { 86 colors[0] = kTitleUrgentGradientStart; 87 colors[1] = kTitleUrgentGradientEnd; 88 } 89 SkShader* s = SkGradientShader::CreateLinear( 90 p, colors, NULL, 2, SkShader::kClamp_TileMode, NULL); 91 paint.setShader(s); 92 // Need to unref shader, otherwise never deleted. 93 s->unref(); 94 canvas->AsCanvasSkia()->drawPath(path, paint); 95 } 96 97 PanelController* panel_controller_; 98}; 99 100static bool resources_initialized; 101static void InitializeResources() { 102 if (resources_initialized) { 103 return; 104 } 105 106 resources_initialized = true; 107 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); 108 gfx::Font base_font = rb.GetFont(ResourceBundle::BaseFont); 109 // Title fonts are the same for active and inactive. 110 inactive_font = new gfx::Font(base_font.DeriveFont(0, gfx::Font::BOLD)); 111 active_font = inactive_font; 112 close_button_n = rb.GetBitmapNamed(IDR_TAB_CLOSE); 113 close_button_m = rb.GetBitmapNamed(IDR_TAB_CLOSE_MASK); 114 close_button_h = rb.GetBitmapNamed(IDR_TAB_CLOSE_H); 115 close_button_p = rb.GetBitmapNamed(IDR_TAB_CLOSE_P); 116 close_button_width = close_button_n->width(); 117 close_button_height = close_button_n->height(); 118} 119 120} // namespace 121 122PanelController::PanelController(Delegate* delegate, 123 GtkWindow* window) 124 : delegate_(delegate), 125 panel_(window), 126 panel_xid_(ui::GetX11WindowFromGtkWidget(GTK_WIDGET(panel_))), 127 title_window_(NULL), 128 title_(NULL), 129 title_content_(NULL), 130 expanded_(true), 131 mouse_down_(false), 132 dragging_(false), 133 client_event_handler_id_(0), 134 focused_(false), 135 urgent_(false) { 136} 137 138void PanelController::Init(bool initial_focus, 139 const gfx::Rect& window_bounds, 140 XID creator_xid, 141 WmIpcPanelUserResizeType resize_type) { 142 gfx::Rect title_bounds(0, 0, window_bounds.width(), kTitleHeight); 143 144 views::Widget::CreateParams params(views::Widget::CreateParams::TYPE_WINDOW); 145 params.transparent = true; 146 title_window_ = views::Widget::CreateWidget(params); 147 title_window_->Init(NULL, title_bounds); 148 gtk_widget_set_size_request(title_window_->GetNativeView(), 149 title_bounds.width(), title_bounds.height()); 150 title_ = title_window_->GetNativeView(); 151 title_xid_ = ui::GetX11WindowFromGtkWidget(title_); 152 153 WmIpc::instance()->SetWindowType( 154 title_, 155 WM_IPC_WINDOW_CHROME_PANEL_TITLEBAR, 156 NULL); 157 std::vector<int> type_params; 158 type_params.push_back(title_xid_); 159 type_params.push_back(expanded_ ? 1 : 0); 160 type_params.push_back(initial_focus ? 1 : 0); 161 type_params.push_back(creator_xid); 162 type_params.push_back(resize_type); 163 WmIpc::instance()->SetWindowType( 164 GTK_WIDGET(panel_), 165 WM_IPC_WINDOW_CHROME_PANEL_CONTENT, 166 &type_params); 167 168 client_event_handler_id_ = g_signal_connect( 169 panel_, "client-event", G_CALLBACK(OnPanelClientEvent), this); 170 171 title_content_ = new TitleContentView(this); 172 title_window_->SetContentsView(title_content_); 173 UpdateTitleBar(); 174 title_window_->Show(); 175} 176 177void PanelController::UpdateTitleBar() { 178 if (!delegate_ || !title_window_) 179 return; 180 title_content_->title_label()->SetText( 181 UTF16ToWideHack(delegate_->GetPanelTitle())); 182 title_content_->title_icon()->SetImage(delegate_->GetPanelIcon()); 183} 184 185void PanelController::SetUrgent(bool urgent) { 186 if (!urgent) 187 urgent_cleared_time_ = base::TimeTicks::Now(); 188 if (urgent == urgent_) 189 return; 190 if (urgent && focused_) 191 return; // Don't set urgency for focused panels. 192 if (urgent && base::TimeTicks::Now() < urgent_cleared_time_ + kSetUrgentDelay) 193 return; // Don't set urgency immediately after clearing it. 194 urgent_ = urgent; 195 if (title_window_) { 196 gtk_window_set_urgency_hint(panel_, urgent ? TRUE : FALSE); 197 title_content_->SchedulePaint(); 198 } 199} 200 201bool PanelController::TitleMousePressed(const views::MouseEvent& event) { 202 if (!event.IsOnlyLeftMouseButton()) 203 return false; 204 GdkEvent* gdk_event = gtk_get_current_event(); 205 if (gdk_event->type != GDK_BUTTON_PRESS) { 206 gdk_event_free(gdk_event); 207 NOTREACHED(); 208 return false; 209 } 210 DCHECK(title_); 211 // Get the last titlebar width that we saw in a ConfigureNotify event -- we 212 // need to give drag positions in terms of the top-right corner of the 213 // titlebar window. See WM_IPC_MESSAGE_WM_NOTIFY_PANEL_DRAGGED's declaration 214 // for details. 215 gint title_width = 1; 216 gtk_window_get_size(GTK_WINDOW(title_), &title_width, NULL); 217 218 GdkEventButton last_button_event = gdk_event->button; 219 mouse_down_ = true; 220 mouse_down_abs_x_ = last_button_event.x_root; 221 mouse_down_abs_y_ = last_button_event.y_root; 222 mouse_down_offset_x_ = event.x() - title_width; 223 mouse_down_offset_y_ = event.y(); 224 dragging_ = false; 225 gdk_event_free(gdk_event); 226 return true; 227} 228 229void PanelController::TitleMouseReleased(const views::MouseEvent& event) { 230 if (event.IsLeftMouseButton()) 231 TitleMouseCaptureLost(); 232} 233 234void PanelController::TitleMouseCaptureLost() { 235 // Only handle clicks that started in our window. 236 if (!mouse_down_) 237 return; 238 239 mouse_down_ = false; 240 if (!dragging_) { 241 if (expanded_) { 242 // Always activate the panel here, even if we are about to minimize it. 243 // This lets panels like GTalk know that they have been acknowledged, so 244 // they don't change the title again (which would trigger SetUrgent). 245 // Activating the panel also clears the urgent state. 246 delegate_->ActivatePanel(); 247 SetState(PanelController::MINIMIZED); 248 } else { 249 // If we're expanding the panel, do so before focusing it. This lets the 250 // window manager know that the panel is being expanded in response to a 251 // user action; see http://crosbug.com/14735. 252 SetState(PanelController::EXPANDED); 253 delegate_->ActivatePanel(); 254 } 255 } else { 256 WmIpc::Message msg(WM_IPC_MESSAGE_WM_NOTIFY_PANEL_DRAG_COMPLETE); 257 msg.set_param(0, panel_xid_); 258 WmIpc::instance()->SendMessage(msg); 259 dragging_ = false; 260 } 261} 262 263void PanelController::SetState(State state) { 264 WmIpc::Message msg(WM_IPC_MESSAGE_WM_SET_PANEL_STATE); 265 msg.set_param(0, panel_xid_); 266 msg.set_param(1, state == EXPANDED); 267 WmIpc::instance()->SendMessage(msg); 268} 269 270bool PanelController::TitleMouseDragged(const views::MouseEvent& event) { 271 if (!mouse_down_) 272 return false; 273 GdkEvent* gdk_event = gtk_get_current_event(); 274 if (gdk_event->type != GDK_MOTION_NOTIFY) { 275 gdk_event_free(gdk_event); 276 NOTREACHED(); 277 return false; 278 } 279 GdkEventMotion last_motion_event = gdk_event->motion; 280 if (!dragging_) { 281 if (views::View::ExceededDragThreshold( 282 last_motion_event.x_root - mouse_down_abs_x_, 283 last_motion_event.y_root - mouse_down_abs_y_)) { 284 dragging_ = true; 285 } 286 } 287 if (dragging_) { 288 WmIpc::Message msg(WM_IPC_MESSAGE_WM_NOTIFY_PANEL_DRAGGED); 289 msg.set_param(0, panel_xid_); 290 msg.set_param(1, last_motion_event.x_root - mouse_down_offset_x_); 291 msg.set_param(2, last_motion_event.y_root - mouse_down_offset_y_); 292 WmIpc::instance()->SendMessage(msg); 293 } 294 gdk_event_free(gdk_event); 295 return true; 296} 297 298// static 299bool PanelController::OnPanelClientEvent( 300 GtkWidget* widget, 301 GdkEventClient* event, 302 PanelController* panel_controller) { 303 return panel_controller->PanelClientEvent(event); 304} 305 306void PanelController::OnFocusIn() { 307 if (title_window_) 308 title_content_->OnFocusIn(); 309 focused_ = true; 310 // Clear urgent when focused. 311 SetUrgent(false); 312} 313 314void PanelController::OnFocusOut() { 315 focused_ = false; 316 if (title_window_) 317 title_content_->OnFocusOut(); 318} 319 320bool PanelController::PanelClientEvent(GdkEventClient* event) { 321 WmIpc::Message msg; 322 WmIpc::instance()->DecodeMessage(*event, &msg); 323 if (msg.type() == WM_IPC_MESSAGE_CHROME_NOTIFY_PANEL_STATE) { 324 bool new_state = msg.param(0); 325 if (expanded_ != new_state) { 326 expanded_ = new_state; 327 State state = new_state ? EXPANDED : MINIMIZED; 328 NotificationService::current()->Notify( 329 NotificationType::PANEL_STATE_CHANGED, 330 Source<PanelController>(this), 331 Details<State>(&state)); 332 } 333 } 334 return true; 335} 336 337void PanelController::Close() { 338 if (client_event_handler_id_ > 0) { 339 g_signal_handler_disconnect(panel_, client_event_handler_id_); 340 client_event_handler_id_ = 0; 341 } 342 // ignore if the title window is already closed. 343 if (title_window_) { 344 title_window_->Close(); 345 title_window_ = NULL; 346 title_ = NULL; 347 title_content_->OnClose(); 348 title_content_ = NULL; 349 } 350} 351 352void PanelController::OnCloseButtonPressed() { 353 DCHECK(title_content_); 354 if (title_window_) { 355 if (delegate_) { 356 if (!delegate_->CanClosePanel()) 357 return; 358 delegate_->ClosePanel(); 359 } 360 Close(); 361 } 362} 363 364PanelController::TitleContentView::TitleContentView( 365 PanelController* panel_controller) 366 : panel_controller_(panel_controller) { 367 VLOG(1) << "panel: c " << this; 368 InitializeResources(); 369 close_button_ = new views::ImageButton(this); 370 close_button_->SetImage(views::CustomButton::BS_NORMAL, close_button_n); 371 close_button_->SetImage(views::CustomButton::BS_HOT, close_button_h); 372 close_button_->SetImage(views::CustomButton::BS_PUSHED, close_button_p); 373 close_button_->SetBackground( 374 kTitleCloseButtonColor, close_button_n, close_button_m); 375 AddChildView(close_button_); 376 377 title_icon_ = new views::ImageView(); 378 AddChildView(title_icon_); 379 title_label_ = new views::Label(std::wstring()); 380 title_label_->SetHorizontalAlignment(views::Label::ALIGN_LEFT); 381 AddChildView(title_label_); 382 383 set_background( 384 views::Background::CreateBackgroundPainter( 385 true, new TitleBackgroundPainter(panel_controller))); 386 OnFocusOut(); 387} 388 389void PanelController::TitleContentView::Layout() { 390 int close_button_x = bounds().width() - 391 (close_button_width + kTitleCloseButtonPad); 392 close_button_->SetBounds( 393 close_button_x, 394 (bounds().height() - close_button_height) / 2, 395 close_button_width, 396 close_button_height); 397 title_icon_->SetBounds( 398 kTitleWidthPad, 399 kTitleHeightPad, 400 kTitleIconSize, 401 kTitleIconSize); 402 int title_x = kTitleWidthPad * 2 + kTitleIconSize; 403 title_label_->SetBounds( 404 title_x, 405 0, 406 close_button_x - (title_x + kTitleCloseButtonPad), 407 bounds().height()); 408} 409 410bool PanelController::TitleContentView::OnMousePressed( 411 const views::MouseEvent& event) { 412 return panel_controller_->TitleMousePressed(event); 413} 414 415void PanelController::TitleContentView::OnMouseReleased( 416 const views::MouseEvent& event) { 417 panel_controller_->TitleMouseReleased(event); 418} 419 420void PanelController::TitleContentView::OnMouseCaptureLost() { 421 panel_controller_->TitleMouseCaptureLost(); 422} 423 424bool PanelController::TitleContentView::OnMouseDragged( 425 const views::MouseEvent& event) { 426 return panel_controller_->TitleMouseDragged(event); 427} 428 429void PanelController::TitleContentView::OnFocusIn() { 430 title_label_->SetColor(kTitleActiveColor); 431 title_label_->SetFont(*active_font); 432 Layout(); 433 SchedulePaint(); 434} 435 436void PanelController::TitleContentView::OnFocusOut() { 437 title_label_->SetColor(kTitleInactiveColor); 438 title_label_->SetFont(*inactive_font); 439 Layout(); 440 SchedulePaint(); 441} 442 443void PanelController::TitleContentView::OnClose() { 444 panel_controller_ = NULL; 445} 446 447void PanelController::TitleContentView::ButtonPressed( 448 views::Button* sender, const views::Event& event) { 449 if (panel_controller_ && sender == close_button_) 450 panel_controller_->OnCloseButtonPressed(); 451} 452 453PanelController::TitleContentView::~TitleContentView() { 454 VLOG(1) << "panel: delete " << this; 455} 456 457} // namespace chromeos 458