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 34 namespace chromeos { 35 36 static int close_button_width; 37 static int close_button_height; 38 static SkBitmap* close_button_n; 39 static SkBitmap* close_button_m; 40 static SkBitmap* close_button_h; 41 static SkBitmap* close_button_p; 42 static gfx::Font* active_font = NULL; 43 static gfx::Font* inactive_font = NULL; 44 45 namespace { 46 47 const int kTitleHeight = 24; 48 const int kTitleIconSize = 16; 49 const int kTitleWidthPad = 4; 50 const int kTitleHeightPad = 4; 51 const int kTitleCornerRadius = 4; 52 const int kTitleCloseButtonPad = 6; 53 const SkColor kTitleActiveGradientStart = SK_ColorWHITE; 54 const SkColor kTitleActiveGradientEnd = 0xffe7edf1; 55 const SkColor kTitleUrgentGradientStart = 0xfffea044; 56 const SkColor kTitleUrgentGradientEnd = 0xfffa983a; 57 const SkColor kTitleActiveColor = SK_ColorBLACK; 58 const SkColor kTitleInactiveColor = SK_ColorBLACK; 59 const SkColor kTitleCloseButtonColor = SK_ColorBLACK; 60 // Delay before the urgency can be set after it has been cleared. 61 const base::TimeDelta kSetUrgentDelay = base::TimeDelta::FromMilliseconds(500); 62 63 // Used to draw the background of the panel title window. 64 class 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 100 static bool resources_initialized; 101 static 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 122 PanelController::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 138 void 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 177 void 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 185 void 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 201 bool 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 229 void PanelController::TitleMouseReleased(const views::MouseEvent& event) { 230 if (event.IsLeftMouseButton()) 231 TitleMouseCaptureLost(); 232 } 233 234 void 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 263 void 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 270 bool 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 299 bool PanelController::OnPanelClientEvent( 300 GtkWidget* widget, 301 GdkEventClient* event, 302 PanelController* panel_controller) { 303 return panel_controller->PanelClientEvent(event); 304 } 305 306 void PanelController::OnFocusIn() { 307 if (title_window_) 308 title_content_->OnFocusIn(); 309 focused_ = true; 310 // Clear urgent when focused. 311 SetUrgent(false); 312 } 313 314 void PanelController::OnFocusOut() { 315 focused_ = false; 316 if (title_window_) 317 title_content_->OnFocusOut(); 318 } 319 320 bool 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 337 void 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 352 void 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 364 PanelController::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 389 void 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 410 bool PanelController::TitleContentView::OnMousePressed( 411 const views::MouseEvent& event) { 412 return panel_controller_->TitleMousePressed(event); 413 } 414 415 void PanelController::TitleContentView::OnMouseReleased( 416 const views::MouseEvent& event) { 417 panel_controller_->TitleMouseReleased(event); 418 } 419 420 void PanelController::TitleContentView::OnMouseCaptureLost() { 421 panel_controller_->TitleMouseCaptureLost(); 422 } 423 424 bool PanelController::TitleContentView::OnMouseDragged( 425 const views::MouseEvent& event) { 426 return panel_controller_->TitleMouseDragged(event); 427 } 428 429 void PanelController::TitleContentView::OnFocusIn() { 430 title_label_->SetColor(kTitleActiveColor); 431 title_label_->SetFont(*active_font); 432 Layout(); 433 SchedulePaint(); 434 } 435 436 void PanelController::TitleContentView::OnFocusOut() { 437 title_label_->SetColor(kTitleInactiveColor); 438 title_label_->SetFont(*inactive_font); 439 Layout(); 440 SchedulePaint(); 441 } 442 443 void PanelController::TitleContentView::OnClose() { 444 panel_controller_ = NULL; 445 } 446 447 void PanelController::TitleContentView::ButtonPressed( 448 views::Button* sender, const views::Event& event) { 449 if (panel_controller_ && sender == close_button_) 450 panel_controller_->OnCloseButtonPressed(); 451 } 452 453 PanelController::TitleContentView::~TitleContentView() { 454 VLOG(1) << "panel: delete " << this; 455 } 456 457 } // namespace chromeos 458