| // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/chromeos/frame/panel_controller.h" |
| |
| #include <vector> |
| |
| #include "base/logging.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "base/memory/singleton.h" |
| #include "base/string_util.h" |
| #include "base/time.h" |
| #include "base/utf_string_conversions.h" |
| #include "chrome/browser/chromeos/wm_ipc.h" |
| #include "content/common/notification_service.h" |
| #include "grit/app_resources.h" |
| #include "grit/generated_resources.h" |
| #include "grit/theme_resources.h" |
| #include "third_party/cros/chromeos_wm_ipc_enums.h" |
| #include "third_party/skia/include/effects/SkBlurMaskFilter.h" |
| #include "third_party/skia/include/effects/SkGradientShader.h" |
| #include "ui/base/resource/resource_bundle.h" |
| #include "ui/gfx/canvas_skia.h" |
| #include "views/controls/button/image_button.h" |
| #include "views/controls/image_view.h" |
| #include "views/controls/label.h" |
| #include "views/events/event.h" |
| #include "views/painter.h" |
| #include "views/view.h" |
| #include "views/widget/widget.h" |
| #include "views/window/window.h" |
| |
| namespace chromeos { |
| |
| static int close_button_width; |
| static int close_button_height; |
| static SkBitmap* close_button_n; |
| static SkBitmap* close_button_m; |
| static SkBitmap* close_button_h; |
| static SkBitmap* close_button_p; |
| static gfx::Font* active_font = NULL; |
| static gfx::Font* inactive_font = NULL; |
| |
| namespace { |
| |
| const int kTitleHeight = 24; |
| const int kTitleIconSize = 16; |
| const int kTitleWidthPad = 4; |
| const int kTitleHeightPad = 4; |
| const int kTitleCornerRadius = 4; |
| const int kTitleCloseButtonPad = 6; |
| const SkColor kTitleActiveGradientStart = SK_ColorWHITE; |
| const SkColor kTitleActiveGradientEnd = 0xffe7edf1; |
| const SkColor kTitleUrgentGradientStart = 0xfffea044; |
| const SkColor kTitleUrgentGradientEnd = 0xfffa983a; |
| const SkColor kTitleActiveColor = SK_ColorBLACK; |
| const SkColor kTitleInactiveColor = SK_ColorBLACK; |
| const SkColor kTitleCloseButtonColor = SK_ColorBLACK; |
| // Delay before the urgency can be set after it has been cleared. |
| const base::TimeDelta kSetUrgentDelay = base::TimeDelta::FromMilliseconds(500); |
| |
| // Used to draw the background of the panel title window. |
| class TitleBackgroundPainter : public views::Painter { |
| public: |
| explicit TitleBackgroundPainter(PanelController* controller) |
| : panel_controller_(controller) { } |
| |
| private: |
| virtual void Paint(int w, int h, gfx::Canvas* canvas) { |
| SkRect rect = {0, 0, w, h}; |
| SkPath path; |
| SkScalar corners[] = { |
| kTitleCornerRadius, kTitleCornerRadius, |
| kTitleCornerRadius, kTitleCornerRadius, |
| 0, 0, |
| 0, 0 |
| }; |
| path.addRoundRect(rect, corners); |
| SkPaint paint; |
| paint.setStyle(SkPaint::kFill_Style); |
| paint.setFlags(SkPaint::kAntiAlias_Flag); |
| SkPoint p[2] = { {0, 0}, {0, h} }; |
| SkColor colors[2] = {kTitleActiveGradientStart, kTitleActiveGradientEnd}; |
| if (panel_controller_->urgent()) { |
| colors[0] = kTitleUrgentGradientStart; |
| colors[1] = kTitleUrgentGradientEnd; |
| } |
| SkShader* s = SkGradientShader::CreateLinear( |
| p, colors, NULL, 2, SkShader::kClamp_TileMode, NULL); |
| paint.setShader(s); |
| // Need to unref shader, otherwise never deleted. |
| s->unref(); |
| canvas->AsCanvasSkia()->drawPath(path, paint); |
| } |
| |
| PanelController* panel_controller_; |
| }; |
| |
| static bool resources_initialized; |
| static void InitializeResources() { |
| if (resources_initialized) { |
| return; |
| } |
| |
| resources_initialized = true; |
| ResourceBundle& rb = ResourceBundle::GetSharedInstance(); |
| gfx::Font base_font = rb.GetFont(ResourceBundle::BaseFont); |
| // Title fonts are the same for active and inactive. |
| inactive_font = new gfx::Font(base_font.DeriveFont(0, gfx::Font::BOLD)); |
| active_font = inactive_font; |
| close_button_n = rb.GetBitmapNamed(IDR_TAB_CLOSE); |
| close_button_m = rb.GetBitmapNamed(IDR_TAB_CLOSE_MASK); |
| close_button_h = rb.GetBitmapNamed(IDR_TAB_CLOSE_H); |
| close_button_p = rb.GetBitmapNamed(IDR_TAB_CLOSE_P); |
| close_button_width = close_button_n->width(); |
| close_button_height = close_button_n->height(); |
| } |
| |
| } // namespace |
| |
| PanelController::PanelController(Delegate* delegate, |
| GtkWindow* window) |
| : delegate_(delegate), |
| panel_(window), |
| panel_xid_(ui::GetX11WindowFromGtkWidget(GTK_WIDGET(panel_))), |
| title_window_(NULL), |
| title_(NULL), |
| title_content_(NULL), |
| expanded_(true), |
| mouse_down_(false), |
| dragging_(false), |
| client_event_handler_id_(0), |
| focused_(false), |
| urgent_(false) { |
| } |
| |
| void PanelController::Init(bool initial_focus, |
| const gfx::Rect& window_bounds, |
| XID creator_xid, |
| WmIpcPanelUserResizeType resize_type) { |
| gfx::Rect title_bounds(0, 0, window_bounds.width(), kTitleHeight); |
| |
| views::Widget::CreateParams params(views::Widget::CreateParams::TYPE_WINDOW); |
| params.transparent = true; |
| title_window_ = views::Widget::CreateWidget(params); |
| title_window_->Init(NULL, title_bounds); |
| gtk_widget_set_size_request(title_window_->GetNativeView(), |
| title_bounds.width(), title_bounds.height()); |
| title_ = title_window_->GetNativeView(); |
| title_xid_ = ui::GetX11WindowFromGtkWidget(title_); |
| |
| WmIpc::instance()->SetWindowType( |
| title_, |
| WM_IPC_WINDOW_CHROME_PANEL_TITLEBAR, |
| NULL); |
| std::vector<int> type_params; |
| type_params.push_back(title_xid_); |
| type_params.push_back(expanded_ ? 1 : 0); |
| type_params.push_back(initial_focus ? 1 : 0); |
| type_params.push_back(creator_xid); |
| type_params.push_back(resize_type); |
| WmIpc::instance()->SetWindowType( |
| GTK_WIDGET(panel_), |
| WM_IPC_WINDOW_CHROME_PANEL_CONTENT, |
| &type_params); |
| |
| client_event_handler_id_ = g_signal_connect( |
| panel_, "client-event", G_CALLBACK(OnPanelClientEvent), this); |
| |
| title_content_ = new TitleContentView(this); |
| title_window_->SetContentsView(title_content_); |
| UpdateTitleBar(); |
| title_window_->Show(); |
| } |
| |
| void PanelController::UpdateTitleBar() { |
| if (!delegate_ || !title_window_) |
| return; |
| title_content_->title_label()->SetText( |
| UTF16ToWideHack(delegate_->GetPanelTitle())); |
| title_content_->title_icon()->SetImage(delegate_->GetPanelIcon()); |
| } |
| |
| void PanelController::SetUrgent(bool urgent) { |
| if (!urgent) |
| urgent_cleared_time_ = base::TimeTicks::Now(); |
| if (urgent == urgent_) |
| return; |
| if (urgent && focused_) |
| return; // Don't set urgency for focused panels. |
| if (urgent && base::TimeTicks::Now() < urgent_cleared_time_ + kSetUrgentDelay) |
| return; // Don't set urgency immediately after clearing it. |
| urgent_ = urgent; |
| if (title_window_) { |
| gtk_window_set_urgency_hint(panel_, urgent ? TRUE : FALSE); |
| title_content_->SchedulePaint(); |
| } |
| } |
| |
| bool PanelController::TitleMousePressed(const views::MouseEvent& event) { |
| if (!event.IsOnlyLeftMouseButton()) |
| return false; |
| GdkEvent* gdk_event = gtk_get_current_event(); |
| if (gdk_event->type != GDK_BUTTON_PRESS) { |
| gdk_event_free(gdk_event); |
| NOTREACHED(); |
| return false; |
| } |
| DCHECK(title_); |
| // Get the last titlebar width that we saw in a ConfigureNotify event -- we |
| // need to give drag positions in terms of the top-right corner of the |
| // titlebar window. See WM_IPC_MESSAGE_WM_NOTIFY_PANEL_DRAGGED's declaration |
| // for details. |
| gint title_width = 1; |
| gtk_window_get_size(GTK_WINDOW(title_), &title_width, NULL); |
| |
| GdkEventButton last_button_event = gdk_event->button; |
| mouse_down_ = true; |
| mouse_down_abs_x_ = last_button_event.x_root; |
| mouse_down_abs_y_ = last_button_event.y_root; |
| mouse_down_offset_x_ = event.x() - title_width; |
| mouse_down_offset_y_ = event.y(); |
| dragging_ = false; |
| gdk_event_free(gdk_event); |
| return true; |
| } |
| |
| void PanelController::TitleMouseReleased(const views::MouseEvent& event) { |
| if (event.IsLeftMouseButton()) |
| TitleMouseCaptureLost(); |
| } |
| |
| void PanelController::TitleMouseCaptureLost() { |
| // Only handle clicks that started in our window. |
| if (!mouse_down_) |
| return; |
| |
| mouse_down_ = false; |
| if (!dragging_) { |
| if (expanded_) { |
| // Always activate the panel here, even if we are about to minimize it. |
| // This lets panels like GTalk know that they have been acknowledged, so |
| // they don't change the title again (which would trigger SetUrgent). |
| // Activating the panel also clears the urgent state. |
| delegate_->ActivatePanel(); |
| SetState(PanelController::MINIMIZED); |
| } else { |
| // If we're expanding the panel, do so before focusing it. This lets the |
| // window manager know that the panel is being expanded in response to a |
| // user action; see http://crosbug.com/14735. |
| SetState(PanelController::EXPANDED); |
| delegate_->ActivatePanel(); |
| } |
| } else { |
| WmIpc::Message msg(WM_IPC_MESSAGE_WM_NOTIFY_PANEL_DRAG_COMPLETE); |
| msg.set_param(0, panel_xid_); |
| WmIpc::instance()->SendMessage(msg); |
| dragging_ = false; |
| } |
| } |
| |
| void PanelController::SetState(State state) { |
| WmIpc::Message msg(WM_IPC_MESSAGE_WM_SET_PANEL_STATE); |
| msg.set_param(0, panel_xid_); |
| msg.set_param(1, state == EXPANDED); |
| WmIpc::instance()->SendMessage(msg); |
| } |
| |
| bool PanelController::TitleMouseDragged(const views::MouseEvent& event) { |
| if (!mouse_down_) |
| return false; |
| GdkEvent* gdk_event = gtk_get_current_event(); |
| if (gdk_event->type != GDK_MOTION_NOTIFY) { |
| gdk_event_free(gdk_event); |
| NOTREACHED(); |
| return false; |
| } |
| GdkEventMotion last_motion_event = gdk_event->motion; |
| if (!dragging_) { |
| if (views::View::ExceededDragThreshold( |
| last_motion_event.x_root - mouse_down_abs_x_, |
| last_motion_event.y_root - mouse_down_abs_y_)) { |
| dragging_ = true; |
| } |
| } |
| if (dragging_) { |
| WmIpc::Message msg(WM_IPC_MESSAGE_WM_NOTIFY_PANEL_DRAGGED); |
| msg.set_param(0, panel_xid_); |
| msg.set_param(1, last_motion_event.x_root - mouse_down_offset_x_); |
| msg.set_param(2, last_motion_event.y_root - mouse_down_offset_y_); |
| WmIpc::instance()->SendMessage(msg); |
| } |
| gdk_event_free(gdk_event); |
| return true; |
| } |
| |
| // static |
| bool PanelController::OnPanelClientEvent( |
| GtkWidget* widget, |
| GdkEventClient* event, |
| PanelController* panel_controller) { |
| return panel_controller->PanelClientEvent(event); |
| } |
| |
| void PanelController::OnFocusIn() { |
| if (title_window_) |
| title_content_->OnFocusIn(); |
| focused_ = true; |
| // Clear urgent when focused. |
| SetUrgent(false); |
| } |
| |
| void PanelController::OnFocusOut() { |
| focused_ = false; |
| if (title_window_) |
| title_content_->OnFocusOut(); |
| } |
| |
| bool PanelController::PanelClientEvent(GdkEventClient* event) { |
| WmIpc::Message msg; |
| WmIpc::instance()->DecodeMessage(*event, &msg); |
| if (msg.type() == WM_IPC_MESSAGE_CHROME_NOTIFY_PANEL_STATE) { |
| bool new_state = msg.param(0); |
| if (expanded_ != new_state) { |
| expanded_ = new_state; |
| State state = new_state ? EXPANDED : MINIMIZED; |
| NotificationService::current()->Notify( |
| NotificationType::PANEL_STATE_CHANGED, |
| Source<PanelController>(this), |
| Details<State>(&state)); |
| } |
| } |
| return true; |
| } |
| |
| void PanelController::Close() { |
| if (client_event_handler_id_ > 0) { |
| g_signal_handler_disconnect(panel_, client_event_handler_id_); |
| client_event_handler_id_ = 0; |
| } |
| // ignore if the title window is already closed. |
| if (title_window_) { |
| title_window_->Close(); |
| title_window_ = NULL; |
| title_ = NULL; |
| title_content_->OnClose(); |
| title_content_ = NULL; |
| } |
| } |
| |
| void PanelController::OnCloseButtonPressed() { |
| DCHECK(title_content_); |
| if (title_window_) { |
| if (delegate_) { |
| if (!delegate_->CanClosePanel()) |
| return; |
| delegate_->ClosePanel(); |
| } |
| Close(); |
| } |
| } |
| |
| PanelController::TitleContentView::TitleContentView( |
| PanelController* panel_controller) |
| : panel_controller_(panel_controller) { |
| VLOG(1) << "panel: c " << this; |
| InitializeResources(); |
| close_button_ = new views::ImageButton(this); |
| close_button_->SetImage(views::CustomButton::BS_NORMAL, close_button_n); |
| close_button_->SetImage(views::CustomButton::BS_HOT, close_button_h); |
| close_button_->SetImage(views::CustomButton::BS_PUSHED, close_button_p); |
| close_button_->SetBackground( |
| kTitleCloseButtonColor, close_button_n, close_button_m); |
| AddChildView(close_button_); |
| |
| title_icon_ = new views::ImageView(); |
| AddChildView(title_icon_); |
| title_label_ = new views::Label(std::wstring()); |
| title_label_->SetHorizontalAlignment(views::Label::ALIGN_LEFT); |
| AddChildView(title_label_); |
| |
| set_background( |
| views::Background::CreateBackgroundPainter( |
| true, new TitleBackgroundPainter(panel_controller))); |
| OnFocusOut(); |
| } |
| |
| void PanelController::TitleContentView::Layout() { |
| int close_button_x = bounds().width() - |
| (close_button_width + kTitleCloseButtonPad); |
| close_button_->SetBounds( |
| close_button_x, |
| (bounds().height() - close_button_height) / 2, |
| close_button_width, |
| close_button_height); |
| title_icon_->SetBounds( |
| kTitleWidthPad, |
| kTitleHeightPad, |
| kTitleIconSize, |
| kTitleIconSize); |
| int title_x = kTitleWidthPad * 2 + kTitleIconSize; |
| title_label_->SetBounds( |
| title_x, |
| 0, |
| close_button_x - (title_x + kTitleCloseButtonPad), |
| bounds().height()); |
| } |
| |
| bool PanelController::TitleContentView::OnMousePressed( |
| const views::MouseEvent& event) { |
| return panel_controller_->TitleMousePressed(event); |
| } |
| |
| void PanelController::TitleContentView::OnMouseReleased( |
| const views::MouseEvent& event) { |
| panel_controller_->TitleMouseReleased(event); |
| } |
| |
| void PanelController::TitleContentView::OnMouseCaptureLost() { |
| panel_controller_->TitleMouseCaptureLost(); |
| } |
| |
| bool PanelController::TitleContentView::OnMouseDragged( |
| const views::MouseEvent& event) { |
| return panel_controller_->TitleMouseDragged(event); |
| } |
| |
| void PanelController::TitleContentView::OnFocusIn() { |
| title_label_->SetColor(kTitleActiveColor); |
| title_label_->SetFont(*active_font); |
| Layout(); |
| SchedulePaint(); |
| } |
| |
| void PanelController::TitleContentView::OnFocusOut() { |
| title_label_->SetColor(kTitleInactiveColor); |
| title_label_->SetFont(*inactive_font); |
| Layout(); |
| SchedulePaint(); |
| } |
| |
| void PanelController::TitleContentView::OnClose() { |
| panel_controller_ = NULL; |
| } |
| |
| void PanelController::TitleContentView::ButtonPressed( |
| views::Button* sender, const views::Event& event) { |
| if (panel_controller_ && sender == close_button_) |
| panel_controller_->OnCloseButtonPressed(); |
| } |
| |
| PanelController::TitleContentView::~TitleContentView() { |
| VLOG(1) << "panel: delete " << this; |
| } |
| |
| } // namespace chromeos |