diff options
author | jennb@chromium.org <jennb@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-08-13 21:27:06 +0000 |
---|---|---|
committer | jennb@chromium.org <jennb@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-08-13 21:27:06 +0000 |
commit | 052125aac0a6238540f72c134c0091f7e8c26eea (patch) | |
tree | aa131d15a1dfb7b5a7bd8367032781a8014b6ba3 | |
parent | fe476b031d96e1cc92321c57e2da967fa510407d (diff) | |
download | chromium_src-052125aac0a6238540f72c134c0091f7e8c26eea.zip chromium_src-052125aac0a6238540f72c134c0091f7e8c26eea.tar.gz chromium_src-052125aac0a6238540f72c134c0091f7e8c26eea.tar.bz2 |
Panels refactor: Support browserless panels on Linux.
BUG=127323
TEST=Enabled tests for refactored panels on Linux
Review URL: https://chromiumcodereview.appspot.com/10831226
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@151353 0039d316-1c4b-4281-b951-d872f2087c98
21 files changed, 1975 insertions, 143 deletions
diff --git a/chrome/browser/ui/gtk/browser_window_gtk.cc b/chrome/browser/ui/gtk/browser_window_gtk.cc index 38739a9..8f10673 100644 --- a/chrome/browser/ui/gtk/browser_window_gtk.cc +++ b/chrome/browser/ui/gtk/browser_window_gtk.cc @@ -4,7 +4,6 @@ #include "chrome/browser/ui/gtk/browser_window_gtk.h" -#include <dlfcn.h> #include <gdk/gdkkeysyms.h> #include <algorithm> @@ -61,6 +60,7 @@ #include "chrome/browser/ui/gtk/global_menu_bar.h" #include "chrome/browser/ui/gtk/gtk_theme_service.h" #include "chrome/browser/ui/gtk/gtk_util.h" +#include "chrome/browser/ui/gtk/gtk_window_util.h" #include "chrome/browser/ui/gtk/infobars/infobar_container_gtk.h" #include "chrome/browser/ui/gtk/infobars/infobar_gtk.h" #include "chrome/browser/ui/gtk/location_bar_view_gtk.h" @@ -152,24 +152,6 @@ const int kCustomFrameBackgroundVerticalOffset = 15; // gtk_window_get_position() after the last GTK configure-event signal. const int kDebounceTimeoutMilliseconds = 100; -// Ubuntu patches their verrsion of GTK+ so that there is always a -// gripper in the bottom right corner of the window. We dynamically -// look up this symbol because it's a non-standard Ubuntu extension to -// GTK+. We always need to disable this feature since we can't -// communicate this to WebKit easily. -typedef void (*gtk_window_set_has_resize_grip_func)(GtkWindow*, gboolean); -gtk_window_set_has_resize_grip_func gtk_window_set_has_resize_grip_sym; - -void EnsureResizeGripFunction() { - static bool resize_grip_looked_up = false; - if (!resize_grip_looked_up) { - resize_grip_looked_up = true; - gtk_window_set_has_resize_grip_sym = - reinterpret_cast<gtk_window_set_has_resize_grip_func>( - dlsym(NULL, "gtk_window_set_has_resize_grip")); - } -} - // Using gtk_window_get_position/size creates a race condition, so only use // this to get the initial bounds. After window creation, we pick up the // normal bounds by connecting to the configure-event signal. @@ -236,30 +218,6 @@ int GetPreHandleCommandId(GdkEventKey* event) { return -1; } -GdkCursorType GdkWindowEdgeToGdkCursorType(GdkWindowEdge edge) { - switch (edge) { - case GDK_WINDOW_EDGE_NORTH_WEST: - return GDK_TOP_LEFT_CORNER; - case GDK_WINDOW_EDGE_NORTH: - return GDK_TOP_SIDE; - case GDK_WINDOW_EDGE_NORTH_EAST: - return GDK_TOP_RIGHT_CORNER; - case GDK_WINDOW_EDGE_WEST: - return GDK_LEFT_SIDE; - case GDK_WINDOW_EDGE_EAST: - return GDK_RIGHT_SIDE; - case GDK_WINDOW_EDGE_SOUTH_WEST: - return GDK_BOTTOM_LEFT_CORNER; - case GDK_WINDOW_EDGE_SOUTH: - return GDK_BOTTOM_SIDE; - case GDK_WINDOW_EDGE_SOUTH_EAST: - return GDK_BOTTOM_RIGHT_CORNER; - default: - NOTREACHED(); - } - return GDK_LAST_CURSOR; -} - // A helper method for setting the GtkWindow size that should be used in place // of calling gtk_window_resize directly. This is done to avoid a WM "feature" // where setting the window size to the monitor size causes the WM to set the @@ -381,9 +339,7 @@ void BrowserWindowGtk::Init() { GDK_POINTER_MOTION_MASK); // Disable the resize gripper on Ubuntu. - EnsureResizeGripFunction(); - if (gtk_window_set_has_resize_grip_sym) - gtk_window_set_has_resize_grip_sym(GTK_WINDOW(window_), FALSE); + gtk_window_util::DisableResizeGrip(window_); // Add this window to its own unique window group to allow for // window-to-parent modality. @@ -1226,15 +1182,18 @@ void BrowserWindowGtk::ShowCreateChromeAppShortcutsDialog( } void BrowserWindowGtk::Cut() { - gtk_util::DoCut(this); + gtk_window_util::DoCut( + window_, chrome::GetActiveWebContents(browser_.get())); } void BrowserWindowGtk::Copy() { - gtk_util::DoCopy(this); + gtk_window_util::DoCopy( + window_, chrome::GetActiveWebContents(browser_.get())); } void BrowserWindowGtk::Paste() { - gtk_util::DoPaste(this); + gtk_window_util::DoPaste( + window_, chrome::GetActiveWebContents(browser_.get())); } void BrowserWindowGtk::ShowInstant(TabContents* preview) { @@ -2258,7 +2217,7 @@ gboolean BrowserWindowGtk::OnMouseMoveEvent(GtkWidget* widget, static_cast<int>(event->y), &edge); GdkCursorType new_cursor = GDK_LAST_CURSOR; if (has_hit_edge) - new_cursor = GdkWindowEdgeToGdkCursorType(edge); + new_cursor = gtk_window_util::GdkWindowEdgeToGdkCursorType(edge); GdkCursorType last_cursor = GDK_LAST_CURSOR; if (frame_cursor_) diff --git a/chrome/browser/ui/gtk/gtk_util.cc b/chrome/browser/ui/gtk/gtk_util.cc index 8839f0a..91c7e08 100644 --- a/chrome/browser/ui/gtk/gtk_util.cc +++ b/chrome/browser/ui/gtk/gtk_util.cc @@ -25,12 +25,9 @@ #include "chrome/browser/profiles/profile_manager.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_list.h" -#include "chrome/browser/ui/browser_tabstrip.h" #include "chrome/browser/ui/browser_window.h" #include "chrome/browser/ui/gtk/browser_window_gtk.h" #include "chrome/browser/ui/gtk/gtk_theme_service.h" -#include "content/public/browser/render_view_host.h" -#include "content/public/browser/web_contents.h" #include "googleurl/src/gurl.h" #include "grit/theme_resources.h" #include "ui/base/gtk/gtk_compat.h" @@ -48,9 +45,6 @@ // These conflict with base/tracked_objects.h, so need to come last. #include <gdk/gdkx.h> // NOLINT -using content::RenderWidgetHost; -using content::WebContents; - namespace { #if defined(GOOGLE_CHROME_BUILD) @@ -278,16 +272,6 @@ gboolean PaintNoBackground(GtkWidget* widget, return TRUE; } -WebContents* GetBrowserWindowSelectedWebContents(BrowserWindow* window) { - BrowserWindowGtk* browser_window = static_cast<BrowserWindowGtk*>( - window); - return chrome::GetActiveWebContents(browser_window->browser()); -} - -GtkWidget* GetBrowserWindowFocusedWidget(BrowserWindow* window) { - return gtk_window_get_focus(window->GetNativeWindow()); -} - } // namespace namespace gtk_util { @@ -1008,38 +992,4 @@ void ApplyMessageDialogQuirks(GtkWidget* dialog) { } } -// Performs Cut/Copy/Paste operation on the |window|. -// If the current render view is focused, then just call the specified |method| -// against the current render view host, otherwise emit the specified |signal| -// against the focused widget. -// TODO(suzhe): This approach does not work for plugins. -void DoCutCopyPaste(BrowserWindow* window, - void (RenderWidgetHost::*method)(), - const char* signal) { - GtkWidget* widget = GetBrowserWindowFocusedWidget(window); - if (widget == NULL) - return; // Do nothing if no focused widget. - - WebContents* current_tab = GetBrowserWindowSelectedWebContents(window); - if (current_tab && widget == current_tab->GetContentNativeView()) { - (current_tab->GetRenderViewHost()->*method)(); - } else { - guint id; - if ((id = g_signal_lookup(signal, G_OBJECT_TYPE(widget))) != 0) - g_signal_emit(widget, id, 0); - } -} - -void DoCut(BrowserWindow* window) { - DoCutCopyPaste(window, &RenderWidgetHost::Cut, "cut-clipboard"); -} - -void DoCopy(BrowserWindow* window) { - DoCutCopyPaste(window, &RenderWidgetHost::Copy, "copy-clipboard"); -} - -void DoPaste(BrowserWindow* window) { - DoCutCopyPaste(window, &RenderWidgetHost::Paste, "paste-clipboard"); -} - } // namespace gtk_util diff --git a/chrome/browser/ui/gtk/gtk_util.h b/chrome/browser/ui/gtk/gtk_util.h index 75d7ee3..9b5f4a5 100644 --- a/chrome/browser/ui/gtk/gtk_util.h +++ b/chrome/browser/ui/gtk/gtk_util.h @@ -308,11 +308,6 @@ void InitLabelSizeRequestAndEllipsizeMode(GtkWidget* label); // gtk_message_dialog_new. void ApplyMessageDialogQuirks(GtkWidget* dialog); -// Performs Cut/Copy/Paste operation on the |window|. -void DoCut(BrowserWindow* window); -void DoCopy(BrowserWindow* window); -void DoPaste(BrowserWindow* window); - } // namespace gtk_util #endif // CHROME_BROWSER_UI_GTK_GTK_UTIL_H_ diff --git a/chrome/browser/ui/gtk/gtk_window_util.cc b/chrome/browser/ui/gtk/gtk_window_util.cc new file mode 100644 index 0000000..91258b2 --- /dev/null +++ b/chrome/browser/ui/gtk/gtk_window_util.cc @@ -0,0 +1,97 @@ +// Copyright (c) 2012 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/ui/gtk/gtk_window_util.h" + +#include <dlfcn.h> +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/web_contents.h" + +using content::RenderWidgetHost; +using content::WebContents; + +namespace gtk_window_util { + +// Performs Cut/Copy/Paste operation on the |window|. +// If the current render view is focused, then just call the specified |method| +// against the current render view host, otherwise emit the specified |signal| +// against the focused widget. +// TODO(suzhe): This approach does not work for plugins. +void DoCutCopyPaste(GtkWindow* window, + WebContents* web_contents, + void (RenderWidgetHost::*method)(), + const char* signal) { + GtkWidget* widget = gtk_window_get_focus(window); + if (widget == NULL) + return; // Do nothing if no focused widget. + + if (web_contents && widget == web_contents->GetContentNativeView()) { + (web_contents->GetRenderViewHost()->*method)(); + } else { + guint id; + if ((id = g_signal_lookup(signal, G_OBJECT_TYPE(widget))) != 0) + g_signal_emit(widget, id, 0); + } +} + +void DoCut(GtkWindow* window, WebContents* web_contents) { + DoCutCopyPaste(window, web_contents, + &RenderWidgetHost::Cut, "cut-clipboard"); +} + +void DoCopy(GtkWindow* window, WebContents* web_contents) { + DoCutCopyPaste(window, web_contents, + &RenderWidgetHost::Copy, "copy-clipboard"); +} + +void DoPaste(GtkWindow* window, WebContents* web_contents) { + DoCutCopyPaste(window, web_contents, + &RenderWidgetHost::Paste, "paste-clipboard"); +} + +// Ubuntu patches their version of GTK+ so that there is always a +// gripper in the bottom right corner of the window. We dynamically +// look up this symbol because it's a non-standard Ubuntu extension to +// GTK+. We always need to disable this feature since we can't +// communicate this to WebKit easily. +typedef void (*gtk_window_set_has_resize_grip_func)(GtkWindow*, gboolean); +gtk_window_set_has_resize_grip_func gtk_window_set_has_resize_grip_sym; + +void DisableResizeGrip(GtkWindow* window) { + static bool resize_grip_looked_up = false; + if (!resize_grip_looked_up) { + resize_grip_looked_up = true; + gtk_window_set_has_resize_grip_sym = + reinterpret_cast<gtk_window_set_has_resize_grip_func>( + dlsym(NULL, "gtk_window_set_has_resize_grip")); + } + if (gtk_window_set_has_resize_grip_sym) + gtk_window_set_has_resize_grip_sym(window, FALSE); +} + +GdkCursorType GdkWindowEdgeToGdkCursorType(GdkWindowEdge edge) { + switch (edge) { + case GDK_WINDOW_EDGE_NORTH_WEST: + return GDK_TOP_LEFT_CORNER; + case GDK_WINDOW_EDGE_NORTH: + return GDK_TOP_SIDE; + case GDK_WINDOW_EDGE_NORTH_EAST: + return GDK_TOP_RIGHT_CORNER; + case GDK_WINDOW_EDGE_WEST: + return GDK_LEFT_SIDE; + case GDK_WINDOW_EDGE_EAST: + return GDK_RIGHT_SIDE; + case GDK_WINDOW_EDGE_SOUTH_WEST: + return GDK_BOTTOM_LEFT_CORNER; + case GDK_WINDOW_EDGE_SOUTH: + return GDK_BOTTOM_SIDE; + case GDK_WINDOW_EDGE_SOUTH_EAST: + return GDK_BOTTOM_RIGHT_CORNER; + default: + NOTREACHED(); + } + return GDK_LAST_CURSOR; +} + +} // namespace gtk_window_util diff --git a/chrome/browser/ui/gtk/gtk_window_util.h b/chrome/browser/ui/gtk/gtk_window_util.h new file mode 100644 index 0000000..8783f1c --- /dev/null +++ b/chrome/browser/ui/gtk/gtk_window_util.h @@ -0,0 +1,32 @@ +// Copyright (c) 2012 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. + +#ifndef CHROME_BROWSER_UI_GTK_GTK_WINDOW_UTIL_H_ +#define CHROME_BROWSER_UI_GTK_GTK_WINDOW_UTIL_H_ + +#include <gtk/gtk.h> + +namespace content { +class WebContents; +} + +namespace gtk_window_util { + +// Performs Cut/Copy/Paste operation on the |window|'s |web_contents|. +void DoCut(GtkWindow* window, content::WebContents* web_contents); +void DoCopy(GtkWindow* window, content::WebContents* web_contents); +void DoPaste(GtkWindow* window, content::WebContents* web_contents); + +// Ubuntu patches their version of GTK+ to that there is always a +// gripper in the bottom right corner of the window. We always need to +// disable this feature since we can't communicate this to WebKit easily. +void DisableResizeGrip(GtkWindow* window); + +// Returns the resize cursor corresponding to the window |edge|. +GdkCursorType GdkWindowEdgeToGdkCursorType(GdkWindowEdge edge); + +} // namespace gtk_window_util + +#endif // CHROME_BROWSER_UI_GTK_GTK_WINDOW_UTIL_H_ + diff --git a/chrome/browser/ui/panels/detached_panel_browsertest.cc b/chrome/browser/ui/panels/detached_panel_browsertest.cc index 901560a..2fab8c0b 100644 --- a/chrome/browser/ui/panels/detached_panel_browsertest.cc +++ b/chrome/browser/ui/panels/detached_panel_browsertest.cc @@ -9,9 +9,6 @@ #include "chrome/browser/ui/panels/panel.h" #include "chrome/browser/ui/panels/panel_manager.h" -// Refactor has only been done for Win and Mac panels so far. -#if defined(OS_WIN) || defined(OS_MACOSX) - class DetachedPanelBrowserTest : public BasePanelBrowserTest { }; @@ -145,5 +142,3 @@ IN_PROC_BROWSER_TEST_F(DetachedPanelBrowserTest, ClickTitlebar) { panel_manager->CloseAll(); } - -#endif // OS_WIN || OS_MACOSX diff --git a/chrome/browser/ui/panels/docked_panel_browsertest.cc b/chrome/browser/ui/panels/docked_panel_browsertest.cc index e16cd9a..f5baac5 100644 --- a/chrome/browser/ui/panels/docked_panel_browsertest.cc +++ b/chrome/browser/ui/panels/docked_panel_browsertest.cc @@ -11,9 +11,6 @@ #include "content/public/browser/notification_service.h" #include "content/public/test/test_utils.h" -// Refactor has only been done for Win and Mac panels so far. -#if defined(OS_WIN) || defined(OS_MACOSX) - class DockedPanelBrowserTest : public BasePanelBrowserTest { public: virtual void SetUpOnMainThread() OVERRIDE { @@ -268,5 +265,3 @@ IN_PROC_BROWSER_TEST_F(DockedPanelBrowserTest, CloseSqueezedPanels) { panel_manager->CloseAll(); } - -#endif // OS_WIN || OS_MACOSX diff --git a/chrome/browser/ui/panels/panel.cc b/chrome/browser/ui/panels/panel.cc index 1f63a80..b4ebc4c 100644 --- a/chrome/browser/ui/panels/panel.cc +++ b/chrome/browser/ui/panels/panel.cc @@ -102,6 +102,7 @@ bool PanelExtensionWindowController::IsVisibleToExtension( Panel::Panel(const std::string& app_name, const gfx::Size& min_size, const gfx::Size& max_size) : app_name_(app_name), + profile_(NULL), panel_strip_(NULL), initialized_(false), min_size_(min_size), @@ -142,6 +143,7 @@ void Panel::Initialize(Profile* profile, const GURL& url, DCHECK_EQ(EXPANDED, expansion_state_); DCHECK(!bounds.IsEmpty()); initialized_ = true; + profile_ = profile; full_size_ = bounds.size(); native_panel_ = CreateNativePanel(this, bounds); @@ -215,7 +217,7 @@ CommandUpdater* Panel::command_updater() { } Profile* Panel::profile() const { - return extension_window_controller_->profile(); + return profile_; } const std::string Panel::extension_id() const { diff --git a/chrome/browser/ui/panels/panel.h b/chrome/browser/ui/panels/panel.h index edf01e7..8fd099e 100644 --- a/chrome/browser/ui/panels/panel.h +++ b/chrome/browser/ui/panels/panel.h @@ -336,6 +336,8 @@ class Panel : public BaseWindow, // This name should be set when the panel is created. const std::string app_name_; + Profile* profile_; + // Current collection of panels to which this panel belongs. This determines // the panel's screen layout. PanelStrip* panel_strip_; // Owned by PanelManager. diff --git a/chrome/browser/ui/panels/panel_and_desktop_notification_browsertest.cc b/chrome/browser/ui/panels/panel_and_desktop_notification_browsertest.cc index 981bf03..3cf1377 100644 --- a/chrome/browser/ui/panels/panel_and_desktop_notification_browsertest.cc +++ b/chrome/browser/ui/panels/panel_and_desktop_notification_browsertest.cc @@ -18,10 +18,6 @@ #include "content/public/common/show_desktop_notification_params.h" #include "ui/gfx/screen.h" - -// Refactor has only been done for Win and Mac panels so far. -#if defined(OS_WIN) || defined(OS_MACOSX) - // Desktop notification code subscribes to various panel change notifications // so that it knows when to adjusts balloon positions. In order to give // desktop notification code a chance to process the change notifications, @@ -403,5 +399,3 @@ IN_PROC_BROWSER_TEST_F(PanelAndDesktopNotificationTest, InteractWithTwoPanels) { MessageLoopForUI::current()->RunAllPending(); EXPECT_EQ(original_balloon_bottom, GetBalloonBottomPosition(balloon)); } - -#endif // OS_WIN || OS_MACOSX diff --git a/chrome/browser/ui/panels/panel_browsertest.cc b/chrome/browser/ui/panels/panel_browsertest.cc index b14a5fb..9003471 100644 --- a/chrome/browser/ui/panels/panel_browsertest.cc +++ b/chrome/browser/ui/panels/panel_browsertest.cc @@ -24,6 +24,7 @@ #include "chrome/browser/ui/panels/native_panel.h" #include "chrome/browser/ui/panels/panel.h" #include "chrome/browser/ui/panels/panel_manager.h" +#include "chrome/browser/ui/panels/test_panel_active_state_observer.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/browser/web_applications/web_app.h" #include "chrome/common/chrome_notification_types.h" @@ -42,9 +43,6 @@ #include "testing/gtest/include/gtest/gtest.h" #include "ui/gfx/screen.h" -// Refactor has only been done for Win and Mac panels so far. -#if defined(OS_WIN) || defined(OS_MACOSX) - using content::BrowserContext; using content::BrowserThread; using content::DownloadItem; @@ -1275,9 +1273,9 @@ IN_PROC_BROWSER_TEST_F(PanelBrowserTest, FocusLostOnMinimize) { Panel* panel = CreatePanelWithParams(params); EXPECT_EQ(Panel::EXPANDED, panel->expansion_state()); - panel->SetExpansionState(Panel::MINIMIZED); - MessageLoop::current()->RunAllPending(); - WaitForPanelActiveState(panel, SHOW_AS_INACTIVE); + PanelActiveStateObserver signal(panel, false); + panel->Minimize(); + signal.Wait(); panel->Close(); } @@ -1593,4 +1591,3 @@ IN_PROC_BROWSER_TEST_F(PanelBrowserTest, MAYBE_Accelerator) { EXPECT_EQ(0, panel_manager->num_panels()); } -#endif // OS_WIN || OS_MACOSX diff --git a/chrome/browser/ui/panels/panel_drag_browsertest.cc b/chrome/browser/ui/panels/panel_drag_browsertest.cc index 3a9e5b1..e87e878 100644 --- a/chrome/browser/ui/panels/panel_drag_browsertest.cc +++ b/chrome/browser/ui/panels/panel_drag_browsertest.cc @@ -11,9 +11,6 @@ #include "chrome/browser/ui/panels/panel_drag_controller.h" #include "chrome/browser/ui/panels/panel_manager.h" -// Refactor has only been done for Win and Mac panels so far. -#if defined(OS_WIN) || defined(OS_MACOSX) - class PanelDragBrowserTest : public BasePanelBrowserTest { public: PanelDragBrowserTest() : BasePanelBrowserTest() { @@ -1340,5 +1337,3 @@ IN_PROC_BROWSER_TEST_F(PanelDragBrowserTest, DragDetachedPanelToTop) { panel_manager->CloseAll(); } - -#endif // OS_WIN || OS_MACOSX diff --git a/chrome/browser/ui/panels/panel_drag_gtk.h b/chrome/browser/ui/panels/panel_drag_gtk.h index a70e062..2d39333 100644 --- a/chrome/browser/ui/panels/panel_drag_gtk.h +++ b/chrome/browser/ui/panels/panel_drag_gtk.h @@ -43,7 +43,8 @@ class PanelDragGtk { GtkWidget* titlebar_widget); private: - friend class NativePanelTestingGtk; + friend class NativePanelTestingGtk; // legacy + friend class GtkNativePanelTesting; enum DragState { NOT_DRAGGING, diff --git a/chrome/browser/ui/panels/panel_gtk.cc b/chrome/browser/ui/panels/panel_gtk.cc index eeb6039..59bfebc 100644 --- a/chrome/browser/ui/panels/panel_gtk.cc +++ b/chrome/browser/ui/panels/panel_gtk.cc @@ -2,11 +2,1180 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "chrome/browser/ui/panels/panel_gtk.h" + +#include <gdk/gdk.h> +#include <gdk/gdkkeysyms.h> +#include <X11/XF86keysym.h> + +#include "base/bind.h" +#include "base/debug/trace_event.h" #include "base/logging.h" +#include "base/message_loop.h" +#include "base/utf_string_conversions.h" +#include "chrome/app/chrome_command_ids.h" +#include "chrome/browser/ui/app_modal_dialogs/app_modal_dialog_queue.h" +#include "chrome/browser/ui/gtk/custom_button.h" +#include "chrome/browser/ui/gtk/gtk_theme_service.h" +#include "chrome/browser/ui/gtk/gtk_util.h" +#include "chrome/browser/ui/gtk/gtk_window_util.h" #include "chrome/browser/ui/panels/panel.h" +#include "chrome/browser/ui/panels/panel_bounds_animation.h" +#include "chrome/browser/ui/panels/panel_titlebar_gtk.h" +#include "chrome/browser/ui/panels/panel_constants.h" +#include "chrome/browser/ui/panels/panel_drag_gtk.h" +#include "chrome/browser/ui/panels/panel_manager.h" +#include "chrome/browser/web_applications/web_app.h" +#include "chrome/common/chrome_notification_types.h" +#include "content/public/browser/native_web_keyboard_event.h" +#include "content/public/browser/notification_service.h" +#include "content/public/browser/web_contents.h" +#include "grit/theme_resources.h" +#include "grit/ui_resources.h" +#include "ui/base/accelerators/accelerator_gtk.h" +#include "ui/base/gtk/gtk_compat.h" +#include "ui/base/gtk/gtk_expanded_container.h" +#include "ui/base/gtk/gtk_hig_constants.h" +#include "ui/base/x/active_window_watcher_x.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/image/cairo_cached_surface.h" +#include "ui/gfx/image/image.h" + +using content::NativeWebKeyboardEvent; +using content::WebContents; + +namespace { + +const char* kPanelWindowKey = "__PANEL_GTK__"; + +// The number of milliseconds between loading animation frames. +const int kLoadingAnimationFrameTimeMs = 30; + +// The frame border is only visible in restored mode and is hardcoded to 4 px +// on each side regardless of the system window border size. +const int kFrameBorderThickness = 4; +// While resize areas on Windows are normally the same size as the window +// borders, our top area is shrunk by 1 px to make it easier to move the window +// around with our thinner top grabbable strip. (Incidentally, our side and +// bottom resize areas don't match the frame border thickness either -- they +// span the whole nonclient area, so there's no "dead zone" for the mouse.) +const int kTopResizeAdjust = 1; +// In the window corners, the resize areas don't actually expand bigger, but +// the 16 px at the end of each edge triggers diagonal resizing. +const int kResizeAreaCornerSize = 16; + +// Colors used to draw frame background under default theme. +const SkColor kActiveBackgroundDefaultColor = SkColorSetRGB(0x3a, 0x3d, 0x3d); +const SkColor kInactiveBackgroundDefaultColor = SkColorSetRGB(0x7a, 0x7c, 0x7c); +const SkColor kAttentionBackgroundDefaultColor = + SkColorSetRGB(0xff, 0xab, 0x57); +const SkColor kMinimizeBackgroundDefaultColor = SkColorSetRGB(0xf5, 0xf4, 0xf0); +const SkColor kMinimizeBorderDefaultColor = SkColorSetRGB(0xc9, 0xc9, 0xc9); + +// Color used to draw the divider line between the titlebar and the client area. +const SkColor kDividerColor = SkColorSetRGB(0x2a, 0x2c, 0x2c); + +// Set minimium width for window really small. +const int kMinWindowWidth = 26; + +// Table of supported accelerators in Panels. +const struct AcceleratorMapping { + guint keyval; + int command_id; + GdkModifierType modifier_type; +} kAcceleratorMap[] = { + // Window controls. + { GDK_w, IDC_CLOSE_WINDOW, GDK_CONTROL_MASK }, + { GDK_w, IDC_CLOSE_WINDOW, + GdkModifierType(GDK_CONTROL_MASK | GDK_SHIFT_MASK) }, + { GDK_q, IDC_EXIT, GdkModifierType(GDK_CONTROL_MASK | GDK_SHIFT_MASK) }, + + // Zoom level. + { GDK_KP_Add, IDC_ZOOM_PLUS, GDK_CONTROL_MASK }, + { GDK_plus, IDC_ZOOM_PLUS, + GdkModifierType(GDK_CONTROL_MASK | GDK_SHIFT_MASK) }, + { GDK_equal, IDC_ZOOM_PLUS, GDK_CONTROL_MASK }, + { XF86XK_ZoomIn, IDC_ZOOM_PLUS, GdkModifierType(0) }, + { GDK_KP_0, IDC_ZOOM_NORMAL, GDK_CONTROL_MASK }, + { GDK_0, IDC_ZOOM_NORMAL, GDK_CONTROL_MASK }, + { GDK_KP_Subtract, IDC_ZOOM_MINUS, GDK_CONTROL_MASK }, + { GDK_minus, IDC_ZOOM_MINUS, GDK_CONTROL_MASK }, + { GDK_underscore, IDC_ZOOM_MINUS, + GdkModifierType(GDK_CONTROL_MASK | GDK_SHIFT_MASK) }, + { XF86XK_ZoomOut, IDC_ZOOM_MINUS, GdkModifierType(0) }, + + // Navigation. + { GDK_Escape, IDC_STOP, GdkModifierType(0) }, + { XF86XK_Stop, IDC_STOP, GdkModifierType(0) }, + { GDK_r, IDC_RELOAD, GDK_CONTROL_MASK }, + { GDK_r, IDC_RELOAD_IGNORING_CACHE, + GdkModifierType(GDK_CONTROL_MASK|GDK_SHIFT_MASK) }, + { GDK_F5, IDC_RELOAD, GdkModifierType(0) }, + { GDK_F5, IDC_RELOAD_IGNORING_CACHE, GDK_CONTROL_MASK }, + { GDK_F5, IDC_RELOAD_IGNORING_CACHE, GDK_SHIFT_MASK }, + { XF86XK_Reload, IDC_RELOAD, GdkModifierType(0) }, + { XF86XK_Refresh, IDC_RELOAD, GdkModifierType(0) }, + + // Editing. + { GDK_c, IDC_COPY, GDK_CONTROL_MASK }, + { GDK_x, IDC_CUT, GDK_CONTROL_MASK }, + { GDK_v, IDC_PASTE, GDK_CONTROL_MASK }, +}; + +// Table of accelerator mappings to command ids. +typedef std::map<ui::AcceleratorGtk, int> AcceleratorGtkMap; + +const AcceleratorGtkMap& GetAcceleratorTable() { + CR_DEFINE_STATIC_LOCAL(AcceleratorGtkMap, accelerator_table, ()); + if (accelerator_table.empty()) { + for (size_t i = 0; i < arraysize(kAcceleratorMap); ++i) { + const AcceleratorMapping& entry = kAcceleratorMap[i]; + ui::AcceleratorGtk accelerator(entry.keyval, entry.modifier_type); + accelerator_table[accelerator] = entry.command_id; + } + } + return accelerator_table; +} + +gfx::Image* CreateImageForColor(SkColor color) { + gfx::Canvas canvas(gfx::Size(1, 1), ui::SCALE_FACTOR_100P, true); + canvas.DrawColor(color); + return new gfx::Image(gfx::ImageSkia(canvas.ExtractImageRep())); +} + +const gfx::Image* GetActiveBackgroundDefaultImage() { + static gfx::Image* image = NULL; + if (!image) + image = CreateImageForColor(kActiveBackgroundDefaultColor); + return image; +} + +const gfx::Image* GetInactiveBackgroundDefaultImage() { + static gfx::Image* image = NULL; + if (!image) + image = CreateImageForColor(kInactiveBackgroundDefaultColor); + return image; +} + +const gfx::Image* GetAttentionBackgroundDefaultImage() { + static gfx::Image* image = NULL; + if (!image) + image = CreateImageForColor(kAttentionBackgroundDefaultColor); + return image; +} + +const gfx::Image* GetMinimizeBackgroundDefaultImage() { + static gfx::Image* image = NULL; + if (!image) + image = CreateImageForColor(kMinimizeBackgroundDefaultColor); + return image; +} + +// Used to stash a pointer to the Panel window inside the native +// Gtk window for retrieval in static callbacks. +GQuark GetPanelWindowQuarkKey() { + static GQuark quark = g_quark_from_static_string(kPanelWindowKey); + return quark; +} + +// Size of window frame. Empty until first panel has been allocated +// and sized. Frame size won't change for other panels so it can be +// computed once for all panels. +gfx::Size& g_frame_size_ = *(new gfx::Size()); + +} // static NativePanel* Panel::CreateNativePanel(Panel* panel, const gfx::Rect& bounds) { - NOTIMPLEMENTED(); - return NULL; + PanelGtk* panel_gtk = new PanelGtk(panel, bounds); + panel_gtk->Init(); + return panel_gtk; +} + +PanelGtk::PanelGtk(Panel* panel, const gfx::Rect& bounds) + : panel_(panel), + bounds_(bounds), + is_shown_(false), + paint_state_(PAINT_AS_INACTIVE), + is_drawing_attention_(false), + frame_cursor_(NULL), + is_active_(!ui::ActiveWindowWatcherX::WMSupportsActivation()), + window_(NULL), + window_container_(NULL), + window_vbox_(NULL), + render_area_event_box_(NULL), + contents_expanded_(NULL), + accel_group_(NULL) { +} + +PanelGtk::~PanelGtk() { + ui::ActiveWindowWatcherX::RemoveObserver(this); +} + +void PanelGtk::Init() { + ui::ActiveWindowWatcherX::AddObserver(this); + + window_ = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL)); + g_object_set_qdata(G_OBJECT(window_), GetPanelWindowQuarkKey(), this); + gtk_widget_add_events(GTK_WIDGET(window_), GDK_BUTTON_PRESS_MASK | + GDK_POINTER_MOTION_MASK); + gtk_window_set_decorated(window_, false); + // Keep the window always on top. + gtk_window_set_keep_above(window_, TRUE); + // Show the window on all the virtual desktops. + gtk_window_stick(window_); + // Do not show an icon in the task bar. Window operations such as close, + // minimize etc. can only be done from the panel UI. + gtk_window_set_skip_taskbar_hint(window_, TRUE); + + // Disable the resize gripper on Ubuntu. + gtk_window_util::DisableResizeGrip(window_); + + // Add this window to its own unique window group to allow for + // window-to-parent modality. + gtk_window_group_add_window(gtk_window_group_new(), window_); + g_object_unref(gtk_window_get_group(window_)); + + // Set minimum height for the window. + GdkGeometry hints; + hints.min_height = panel::kMinimizedPanelHeight; + hints.min_width = kMinWindowWidth; + gtk_window_set_geometry_hints( + window_, GTK_WIDGET(window_), &hints, GDK_HINT_MIN_SIZE); + + // Connect signal handlers to the window. + g_signal_connect(window_, "delete-event", + G_CALLBACK(OnMainWindowDeleteEventThunk), this); + g_signal_connect(window_, "destroy", + G_CALLBACK(OnMainWindowDestroyThunk), this); + g_signal_connect(window_, "configure-event", + G_CALLBACK(OnConfigureThunk), this); + g_signal_connect(window_, "key-press-event", + G_CALLBACK(OnKeyPressThunk), this); + g_signal_connect(window_, "motion-notify-event", + G_CALLBACK(OnMouseMoveEventThunk), this); + g_signal_connect(window_, "button-press-event", + G_CALLBACK(OnButtonPressEventThunk), this); + + // This vbox contains the titlebar and the render area, but not + // the custom frame border. + window_vbox_ = gtk_vbox_new(FALSE, 0); + gtk_widget_show(window_vbox_); + + // TODO(jennb): add GlobalMenuBar after refactoring out Browser. + + // The window container draws the custom browser frame. + window_container_ = gtk_alignment_new(0.0, 0.0, 1.0, 1.0); + gtk_widget_set_name(window_container_, "chrome-custom-frame-border"); + gtk_widget_set_app_paintable(window_container_, TRUE); + gtk_widget_set_double_buffered(window_container_, FALSE); + gtk_widget_set_redraw_on_allocate(window_container_, TRUE); + gtk_alignment_set_padding(GTK_ALIGNMENT(window_container_), 1, + kFrameBorderThickness, kFrameBorderThickness, kFrameBorderThickness); + g_signal_connect(window_container_, "expose-event", + G_CALLBACK(OnCustomFrameExposeThunk), this); + gtk_container_add(GTK_CONTAINER(window_container_), window_vbox_); + + // Build the titlebar. + titlebar_.reset(new PanelTitlebarGtk(this)); + titlebar_->Init(); + gtk_box_pack_start(GTK_BOX(window_vbox_), titlebar_->widget(), FALSE, FALSE, + 0); + g_signal_connect(titlebar_->widget(), "button-press-event", + G_CALLBACK(OnTitlebarButtonPressEventThunk), this); + g_signal_connect(titlebar_->widget(), "button-release-event", + G_CALLBACK(OnTitlebarButtonReleaseEventThunk), this); + + contents_expanded_ = gtk_expanded_container_new(); + gtk_widget_show(contents_expanded_); + + render_area_event_box_ = gtk_event_box_new(); + // Set a white background so during startup the user sees white in the + // content area before we get a WebContents in place. + gtk_widget_modify_bg(render_area_event_box_, GTK_STATE_NORMAL, + &ui::kGdkWhite); + gtk_container_add(GTK_CONTAINER(render_area_event_box_), + contents_expanded_); + gtk_widget_show(render_area_event_box_); + gtk_box_pack_end(GTK_BOX(window_vbox_), render_area_event_box_, + TRUE, TRUE, 0); + + gtk_container_add(GTK_CONTAINER(window_), window_container_); + gtk_widget_show(window_container_); + + ConnectAccelerators(); +} + +void PanelGtk::UpdateWindowShape(int width, int height) { + // For panels, only top corners are rounded. The bottom corners are not + // rounded because panels are aligned to the bottom edge of the screen. + GdkRectangle top_top_rect = { 3, 0, width - 6, 1 }; + GdkRectangle top_mid_rect = { 1, 1, width - 2, 2 }; + GdkRectangle mid_rect = { 0, 3, width, height - 3 }; + GdkRegion* mask = gdk_region_rectangle(&top_top_rect); + gdk_region_union_with_rect(mask, &top_mid_rect); + gdk_region_union_with_rect(mask, &mid_rect); + gdk_window_shape_combine_region( + gtk_widget_get_window(GTK_WIDGET(window_)), mask, 0, 0); + if (mask) + gdk_region_destroy(mask); +} + +gboolean PanelGtk::OnConfigure(GtkWidget* widget, + GdkEventConfigure* event) { + // When the window moves, we'll get multiple configure-event signals. We can + // also get events when the bounds haven't changed, but the window's stacking + // has, which we aren't interested in. http://crbug.com/70125 + gfx::Size new_size(event->width, event->height); + if (new_size == configure_size_) + return FALSE; + + UpdateWindowShape(event->width, event->height); + configure_size_ = new_size; + + if (!g_frame_size_.IsEmpty()) + return FALSE; + + // Save the frame size allocated by the system after as the + // frame size will be affected when we shrink the panel smaller + // than the frame (e.g. when the panel is minimized). + g_frame_size_ = GetNonClientFrameSize(); + panel_->OnWindowSizeAvailable(); + + content::NotificationService::current()->Notify( + chrome::NOTIFICATION_PANEL_WINDOW_SIZE_KNOWN, + content::Source<Panel>(panel_.get()), + content::NotificationService::NoDetails()); + + return FALSE; +} + +void PanelGtk::ConnectAccelerators() { + accel_group_ = gtk_accel_group_new(); + gtk_window_add_accel_group(window_, accel_group_); + + const AcceleratorGtkMap& accelerator_table = GetAcceleratorTable(); + for (AcceleratorGtkMap::const_iterator iter = accelerator_table.begin(); + iter != accelerator_table.end(); ++iter) { + gtk_accel_group_connect( + accel_group_, + iter->first.GetGdkKeyCode(), + static_cast<GdkModifierType>(iter->first.modifiers()), + GtkAccelFlags(0), + g_cclosure_new(G_CALLBACK(OnGtkAccelerator), + GINT_TO_POINTER(iter->second), NULL)); + } +} + +void PanelGtk::DisconnectAccelerators() { + // Disconnecting the keys we connected to our accelerator group frees the + // closures allocated in ConnectAccelerators. + const AcceleratorGtkMap& accelerator_table = GetAcceleratorTable(); + for (AcceleratorGtkMap::const_iterator iter = accelerator_table.begin(); + iter != accelerator_table.end(); ++iter) { + gtk_accel_group_disconnect_key(accel_group_, + iter->first.GetGdkKeyCode(), + static_cast<GdkModifierType>(iter->first.modifiers())); + } + gtk_window_remove_accel_group(window_, accel_group_); + g_object_unref(accel_group_); + accel_group_ = NULL; +} + +// static +gboolean PanelGtk::OnGtkAccelerator(GtkAccelGroup* accel_group, + GObject* acceleratable, + guint keyval, + GdkModifierType modifier, + void* user_data) { + DCHECK(acceleratable); + int command_id = GPOINTER_TO_INT(user_data); + PanelGtk* panel_gtk = static_cast<PanelGtk*>( + g_object_get_qdata(acceleratable, GetPanelWindowQuarkKey())); + return panel_gtk->panel()->ExecuteCommandIfEnabled(command_id); +} + +gboolean PanelGtk::OnKeyPress(GtkWidget* widget, GdkEventKey* event) { + // No way to deactivate a window in GTK, so ignore input if window + // is supposed to be 'inactive'. See comments in DeactivatePanel(). + if (!is_active_) + return TRUE; + + // Propagate the key event to child widget first, so we don't override + // their accelerators. + if (!gtk_window_propagate_key_event(GTK_WINDOW(widget), event)) { + if (!gtk_window_activate_key(GTK_WINDOW(widget), event)) { + gtk_bindings_activate_event(GTK_OBJECT(widget), event); + } + } + return TRUE; +} + +bool PanelGtk::UsingDefaultTheme() const { + // No theme is provided for attention painting. + if (paint_state_ == PAINT_FOR_ATTENTION) + return true; + + GtkThemeService* theme_provider = GtkThemeService::GetFrom(panel_->profile()); + return theme_provider->UsingDefaultTheme() || + theme_provider->UsingNativeTheme(); +} + +bool PanelGtk::GetWindowEdge(int x, int y, GdkWindowEdge* edge) const { + // Only detect the window edge when panels can be resized by the user. + // This method is used by the base class to detect when the cursor has + // hit the window edge in order to change the cursor to a resize cursor + // and to detect when to initiate a resize drag. + panel::Resizability resizability = panel_->CanResizeByMouse(); + if (panel::NOT_RESIZABLE == resizability) + return false; + + if (x < kFrameBorderThickness) { + // Left edge. + if (y < kResizeAreaCornerSize - kTopResizeAdjust) { + *edge = GDK_WINDOW_EDGE_NORTH_WEST; + } else if (y < bounds_.height() - kResizeAreaCornerSize) { + *edge = GDK_WINDOW_EDGE_WEST; + } else { + *edge = GDK_WINDOW_EDGE_SOUTH_WEST; + } + } else if (x < bounds_.width() - kFrameBorderThickness) { + if (y < kFrameBorderThickness - kTopResizeAdjust) { + // Top edge. + if (x < kResizeAreaCornerSize) { + *edge = GDK_WINDOW_EDGE_NORTH_WEST; + } else if (x < bounds_.width() - kResizeAreaCornerSize) { + *edge = GDK_WINDOW_EDGE_NORTH; + } else { + *edge = GDK_WINDOW_EDGE_NORTH_EAST; + } + } else if (y < bounds_.height() - kFrameBorderThickness) { + // Ignore the middle content area. + return false; + } else { + // Bottom edge. + if (x < kResizeAreaCornerSize) { + *edge = GDK_WINDOW_EDGE_SOUTH_WEST; + } else if (x < bounds_.width() - kResizeAreaCornerSize) { + *edge = GDK_WINDOW_EDGE_SOUTH; + } else { + *edge = GDK_WINDOW_EDGE_SOUTH_EAST; + } + } + } else { + // Right edge. + if (y < kResizeAreaCornerSize - kTopResizeAdjust) { + *edge = GDK_WINDOW_EDGE_NORTH_EAST; + } else if (y < bounds_.height() - kResizeAreaCornerSize) { + *edge = GDK_WINDOW_EDGE_EAST; + } else { + *edge = GDK_WINDOW_EDGE_SOUTH_EAST; + } + } + + // Special handling if bottom edge is not resizable. + if (panel::RESIZABLE_ALL_SIDES_EXCEPT_BOTTOM == resizability) { + if (*edge == GDK_WINDOW_EDGE_SOUTH) + return FALSE; + if (*edge == GDK_WINDOW_EDGE_SOUTH_WEST) + *edge = GDK_WINDOW_EDGE_WEST; + else if (*edge == GDK_WINDOW_EDGE_SOUTH_EAST) + *edge = GDK_WINDOW_EDGE_EAST; + } + + return true; +} + +const gfx::Image* PanelGtk::GetFrameBackground() const { + return UsingDefaultTheme() ? + GetDefaultFrameBackground() : GetThemedFrameBackground(); +} + +const gfx::Image* PanelGtk::GetDefaultFrameBackground() const { + switch (paint_state_) { + case PAINT_AS_INACTIVE: + return GetInactiveBackgroundDefaultImage(); + case PAINT_AS_ACTIVE: + return GetActiveBackgroundDefaultImage(); + case PAINT_AS_MINIMIZED: + return GetMinimizeBackgroundDefaultImage(); + case PAINT_FOR_ATTENTION: + return GetAttentionBackgroundDefaultImage(); + default: + NOTREACHED(); + return GetInactiveBackgroundDefaultImage(); + } +} + +const gfx::Image* PanelGtk::GetThemedFrameBackground() const { + GtkThemeService* theme_provider = GtkThemeService::GetFrom(panel_->profile()); + return theme_provider->GetImageNamed(paint_state_ == PAINT_AS_ACTIVE ? + IDR_THEME_TOOLBAR : IDR_THEME_TAB_BACKGROUND); +} + +gboolean PanelGtk::OnCustomFrameExpose(GtkWidget* widget, + GdkEventExpose* event) { + TRACE_EVENT0("ui::gtk", "PanelGtk::OnCustomFrameExpose"); + cairo_t* cr = gdk_cairo_create(gtk_widget_get_window(widget)); + gdk_cairo_rectangle(cr, &event->area); + cairo_clip(cr); + + // Update the painting state. + int window_height = gdk_window_get_height(gtk_widget_get_window(widget)); + if (is_drawing_attention_) + paint_state_ = PAINT_FOR_ATTENTION; + else if (window_height <= panel::kMinimizedPanelHeight) + paint_state_ = PAINT_AS_MINIMIZED; + else if (is_active_) + paint_state_ = PAINT_AS_ACTIVE; + else + paint_state_ = PAINT_AS_INACTIVE; + + // Draw the background. + gfx::CairoCachedSurface* surface = GetFrameBackground()->ToCairo(); + surface->SetSource(cr, widget, 0, 0); + cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT); + cairo_rectangle(cr, event->area.x, event->area.y, + event->area.width, event->area.height); + cairo_fill(cr); + + // Draw the divider only if we're showing more than titlebar. + if (window_height > panel::kTitlebarHeight) { + cairo_set_source_rgb(cr, + SkColorGetR(kDividerColor) / 255.0, + SkColorGetG(kDividerColor) / 255.0, + SkColorGetB(kDividerColor) / 255.0); + cairo_rectangle(cr, 0, panel::kTitlebarHeight - 1, bounds_.width(), 1); + cairo_fill(cr); + } + + // Draw the border for the minimized panel only. + if (paint_state_ == PAINT_AS_MINIMIZED) { + cairo_move_to(cr, 0, 3); + cairo_line_to(cr, 1, 2); + cairo_line_to(cr, 1, 1); + cairo_line_to(cr, 2, 1); + cairo_line_to(cr, 3, 0); + cairo_line_to(cr, event->area.width - 3, 0); + cairo_line_to(cr, event->area.width - 2, 1); + cairo_line_to(cr, event->area.width - 1, 1); + cairo_line_to(cr, event->area.width - 1, 2); + cairo_line_to(cr, event->area.width - 1, 3); + cairo_line_to(cr, event->area.width - 1, event->area.height - 1); + cairo_line_to(cr, 0, event->area.height - 1); + cairo_close_path(cr); + cairo_set_source_rgb(cr, + SkColorGetR(kMinimizeBorderDefaultColor) / 255.0, + SkColorGetG(kMinimizeBorderDefaultColor) / 255.0, + SkColorGetB(kMinimizeBorderDefaultColor) / 255.0); + cairo_set_line_width(cr, 1.0); + cairo_stroke(cr); + } + + cairo_destroy(cr); + + return FALSE; // Allow subwidgets to paint. +} + +void PanelGtk::EnsureDragHelperCreated() { + if (drag_helper_.get()) + return; + + drag_helper_.reset(new PanelDragGtk(panel_.get())); + gtk_box_pack_end(GTK_BOX(window_vbox_), drag_helper_->widget(), + FALSE, FALSE, 0); +} + +gboolean PanelGtk::OnTitlebarButtonPressEvent( + GtkWidget* widget, GdkEventButton* event) { + if (event->button != 1) + return TRUE; + if (event->type != GDK_BUTTON_PRESS) + return TRUE; + + gdk_window_raise(gtk_widget_get_window(GTK_WIDGET(window_))); + EnsureDragHelperCreated(); + drag_helper_->InitialTitlebarMousePress(event, titlebar_->widget()); + return TRUE; +} + +gboolean PanelGtk::OnTitlebarButtonReleaseEvent( + GtkWidget* widget, GdkEventButton* event) { + if (event->button != 1) + return TRUE; + + panel_->OnTitlebarClicked((event->state & GDK_CONTROL_MASK) ? + panel::APPLY_TO_ALL : panel::NO_MODIFIER); + return TRUE; +} + +gboolean PanelGtk::OnMouseMoveEvent(GtkWidget* widget, + GdkEventMotion* event) { + // This method is used to update the mouse cursor when over the edge of the + // custom frame. If we're over some other widget, do nothing. + if (event->window != gtk_widget_get_window(widget)) { + // Reset the cursor. + if (frame_cursor_) { + frame_cursor_ = NULL; + gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(window_)), NULL); + } + return FALSE; + } + + // Update the cursor if we're on the custom frame border. + GdkWindowEdge edge; + bool has_hit_edge = GetWindowEdge(static_cast<int>(event->x), + static_cast<int>(event->y), &edge); + GdkCursorType new_cursor = has_hit_edge ? + gtk_window_util::GdkWindowEdgeToGdkCursorType(edge) : GDK_LAST_CURSOR; + GdkCursorType last_cursor = + frame_cursor_ ? frame_cursor_->type : GDK_LAST_CURSOR; + + if (last_cursor != new_cursor) { + frame_cursor_ = has_hit_edge ? gfx::GetCursor(new_cursor) : NULL; + gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(window_)), + frame_cursor_); + } + return FALSE; +} + +gboolean PanelGtk::OnButtonPressEvent(GtkWidget* widget, + GdkEventButton* event) { + if (event->button != 1 || event->type != GDK_BUTTON_PRESS) + return FALSE; + + // No way to deactivate a window in GTK, so we pretended it is deactivated. + // See comments in DeactivatePanel(). + // Mouse click anywhere in window should re-activate window so do it now. + if (!is_active_) + panel_->Activate(); + + // Make the button press coordinate relative to the panel window. + int win_x, win_y; + GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window_)); + gdk_window_get_origin(gdk_window, &win_x, &win_y); + + GdkWindowEdge edge; + gfx::Point point(static_cast<int>(event->x_root - win_x), + static_cast<int>(event->y_root - win_y)); + bool has_hit_edge = GetWindowEdge(point.x(), point.y(), &edge); + if (has_hit_edge) { + gdk_window_raise(gdk_window); + EnsureDragHelperCreated(); + // Resize cursor was set by PanelGtk when mouse moved over window edge. + GdkCursor* cursor = + gdk_window_get_cursor(gtk_widget_get_window(GTK_WIDGET(window_))); + drag_helper_->InitialWindowEdgeMousePress(event, cursor, edge); + return TRUE; + } + + return FALSE; // Continue to propagate the event. +} + +void PanelGtk::ActiveWindowChanged(GdkWindow* active_window) { + // Do nothing if we're in the process of closing the browser window. + if (!window_) + return; + + bool is_active = gtk_widget_get_window(GTK_WIDGET(window_)) == active_window; + if (is_active == is_active_) + return; // State did not change. + + if (is_active) { + // If there's an app modal dialog (e.g., JS alert), try to redirect + // the user's attention to the window owning the dialog. + if (AppModalDialogQueue::GetInstance()->HasActiveDialog()) { + AppModalDialogQueue::GetInstance()->ActivateModalDialog(); + return; + } + } + + is_active_ = is_active; + titlebar_->UpdateTextColor(); + InvalidateWindow(); + panel_->OnActiveStateChanged(is_active_); +} + +// Callback for the delete event. This event is fired when the user tries to +// close the window. +gboolean PanelGtk::OnMainWindowDeleteEvent(GtkWidget* widget, + GdkEvent* event) { + ClosePanel(); + + // Return true to prevent the gtk window from being destroyed. Close will + // destroy it for us. + return TRUE; +} + +void PanelGtk::OnMainWindowDestroy(GtkWidget* widget) { + // BUG 8712. When we gtk_widget_destroy() in ClosePanel(), this will emit the + // signal right away, and we will be here (while ClosePanel() is still in the + // call stack). Let stack unwind before deleting the panel. + // + // We don't want to use DeleteSoon() here since it won't work on a nested pump + // (like in UI tests). + MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(&base::DeletePointer<PanelGtk>, this)); +} + +void PanelGtk::ShowPanel() { + gtk_window_present(window_); + RevealPanel(); +} + +void PanelGtk::ShowPanelInactive() { + gtk_window_set_focus_on_map(window_, false); + gtk_widget_show(GTK_WIDGET(window_)); + RevealPanel(); +} + +void PanelGtk::RevealPanel() { + DCHECK(!is_shown_); + is_shown_ = true; + + // Grow the window from the botttom up to produce a 'reveal' animation. + int top = bounds_.bottom() - configure_size_.height(); + StartBoundsAnimation( + gfx::Rect(bounds_.x(), top, bounds_.width(), configure_size_.height()), + bounds_); +} + +gfx::Rect PanelGtk::GetPanelBounds() const { + return bounds_; +} + +void PanelGtk::SetPanelBounds(const gfx::Rect& bounds) { + SetBoundsInternal(bounds, true); +} + +void PanelGtk::SetPanelBoundsInstantly(const gfx::Rect& bounds) { + SetBoundsInternal(bounds, false); +} + +void PanelGtk::SetBoundsInternal(const gfx::Rect& bounds, bool animate) { + if (bounds == bounds_) + return; + + if (!animate) { + // If no animation is in progress, apply bounds change instantly. Otherwise, + // continue the animation with new target bounds. + if (!IsAnimatingBounds()) + gdk_window_move_resize(gtk_widget_get_window(GTK_WIDGET(window_)), + bounds.x(), bounds.y(), + bounds.width(), bounds.height()); + } else if (is_shown_) { + StartBoundsAnimation(bounds_, bounds); + } + + bounds_ = bounds; +} + +void PanelGtk::ClosePanel() { + // We're already closing. Do nothing. + if (!window_) + return; + + if (!panel_->ShouldCloseWindow()) + return; + + if (bounds_animator_.get()) + bounds_animator_.reset(); + + if (drag_helper_.get()) + drag_helper_.reset(); + + if (accel_group_) + DisconnectAccelerators(); + + // Cancel any pending callback from the loading animation timer. + loading_animation_timer_.Stop(); + + if (panel_->GetWebContents()) { + // Hide the window (so it appears to have closed immediately). + // When web contents are destroyed, we will be called back again. + gtk_widget_hide(GTK_WIDGET(window_)); + panel_->OnWindowClosing(); + return; + } + + GtkWidget* window = GTK_WIDGET(window_); + // To help catch bugs in any event handlers that might get fired during the + // destruction, set window_ to NULL before any handlers will run. + window_ = NULL; + + panel_->OnNativePanelClosed(); + + // We don't want GlobalMenuBar handling any notifications or commands after + // the window is destroyed. + // TODO(jennb): global_menu_bar_->Disable(); + gtk_widget_destroy(window); +} + +void PanelGtk::ActivatePanel() { + gtk_window_present(window_); +} + +void PanelGtk::DeactivatePanel() { + gdk_window_lower(gtk_widget_get_window(GTK_WIDGET(window_))); + + // Per ICCCM: http://tronche.com/gui/x/icccm/sec-4.html#s-4.1.7 + // A convention is also required for clients that want to give up the + // input focus. There is no safe value set for them to set the input + // focus to; therefore, they should ignore input material. + // + // No way to deactive a GTK window. Pretend panel is deactivated + // and ignore input. + ActiveWindowChanged(NULL); +} + +bool PanelGtk::IsPanelActive() const { + return is_active_; +} + +void PanelGtk::PreventActivationByOS(bool prevent_activation) { + gtk_window_set_accept_focus(window_, !prevent_activation); +} + +gfx::NativeWindow PanelGtk::GetNativePanelHandle() { + return window_; +} + +void PanelGtk::UpdatePanelTitleBar() { + TRACE_EVENT0("ui::gtk", "PanelGtk::UpdatePanelTitleBar"); + string16 title = panel_->GetWindowTitle(); + gtk_window_set_title(window_, UTF16ToUTF8(title).c_str()); + titlebar_->UpdateTitleAndIcon(); +} + +void PanelGtk::UpdatePanelLoadingAnimations(bool should_animate) { + if (should_animate) { + if (!loading_animation_timer_.IsRunning()) { + // Loads are happening, and the timer isn't running, so start it. + loading_animation_timer_.Start(FROM_HERE, + base::TimeDelta::FromMilliseconds(kLoadingAnimationFrameTimeMs), + this, + &PanelGtk::LoadingAnimationCallback); + } + } else { + if (loading_animation_timer_.IsRunning()) { + loading_animation_timer_.Stop(); + // Loads are now complete, update the state if a task was scheduled. + LoadingAnimationCallback(); + } + } +} + +void PanelGtk::LoadingAnimationCallback() { + titlebar_->UpdateThrobber(panel_->GetWebContents()); +} + +FindBar* PanelGtk::CreatePanelFindBar() { + return NULL; // legacy +} + +void PanelGtk::NotifyPanelOnUserChangedTheme() { + titlebar_->UpdateTextColor(); + InvalidateWindow(); +} + +void PanelGtk::PanelCut() { + gtk_window_util::DoCut(window_, panel_->GetWebContents()); +} + +void PanelGtk::PanelCopy() { + gtk_window_util::DoCopy(window_, panel_->GetWebContents()); +} + +void PanelGtk::PanelPaste() { + gtk_window_util::DoPaste(window_, panel_->GetWebContents()); +} + +void PanelGtk::DrawAttention(bool draw_attention) { + DCHECK((panel_->attention_mode() & Panel::USE_PANEL_ATTENTION) != 0); + + if (is_drawing_attention_ == draw_attention) + return; + + is_drawing_attention_ = draw_attention; + + titlebar_->UpdateTextColor(); + InvalidateWindow(); + + if ((panel_->attention_mode() & Panel::USE_SYSTEM_ATTENTION) != 0) { + // May not be respected by all window managers. + gtk_window_set_urgency_hint(window_, draw_attention); + } +} + +bool PanelGtk::IsDrawingAttention() const { + return is_drawing_attention_; +} + +bool PanelGtk::PreHandlePanelKeyboardEvent( + const NativeWebKeyboardEvent& event, + bool* is_keyboard_shortcut) { + // No need to prehandle as no keys are reserved. + return false; +} + +void PanelGtk::HandlePanelKeyboardEvent( + const NativeWebKeyboardEvent& event) { + GdkEventKey* os_event = &event.os_event->key; + if (os_event && event.type == WebKit::WebInputEvent::RawKeyDown) + gtk_window_activate_key(window_, os_event); +} + +void PanelGtk::FullScreenModeChanged(bool is_full_screen) { + // Nothing to do here as z-order rules for panels ensures that they're below + // any app running in full screen mode. +} + +void PanelGtk::PanelExpansionStateChanging( + Panel::ExpansionState old_state, Panel::ExpansionState new_state) { +} + +void PanelGtk::AttachWebContents(content::WebContents* contents) { + if (!contents) + return; + gfx::NativeView widget = contents->GetNativeView(); + if (widget) { + gtk_container_add(GTK_CONTAINER(contents_expanded_), widget); + gtk_widget_show(widget); + contents->WasShown(); + } +} + +void PanelGtk::DetachWebContents(content::WebContents* contents) { + gfx::NativeView widget = contents->GetNativeView(); + if (widget) { + GtkWidget* parent = gtk_widget_get_parent(widget); + if (parent) { + DCHECK_EQ(parent, contents_expanded_); + gtk_container_remove(GTK_CONTAINER(contents_expanded_), widget); + } + } +} + +Browser* PanelGtk::GetPanelBrowser() const { + return NULL; // legacy +} + +gfx::Size PanelGtk::WindowSizeFromContentSize( + const gfx::Size& content_size) const { + return gfx::Size(content_size.width() + g_frame_size_.width(), + content_size.height() + g_frame_size_.height()); +} + +gfx::Size PanelGtk::ContentSizeFromWindowSize( + const gfx::Size& window_size) const { + return gfx::Size(window_size.width() - g_frame_size_.width(), + window_size.height() - g_frame_size_.height()); +} + +int PanelGtk::TitleOnlyHeight() const { + GtkAllocation allocation; + gtk_widget_get_allocation(titlebar_->widget(), &allocation); + return allocation.height; +} + +void PanelGtk::EnsurePanelFullyVisible() { + gtk_window_present(window_); +} + +void PanelGtk::SetPanelAlwaysOnTop(bool on_top) { + gtk_window_set_keep_above(window_, on_top); +} + +void PanelGtk::EnableResizeByMouse(bool enable) { +} + +void PanelGtk::UpdatePanelMinimizeRestoreButtonVisibility() { + titlebar_->UpdateMinimizeRestoreButtonVisibility(); +} + +void PanelGtk::StartBoundsAnimation( + const gfx::Rect& from_bounds, const gfx::Rect& to_bounds) { + animation_start_bounds_ = IsAnimatingBounds() ? + last_animation_progressed_bounds_ : from_bounds; + + bounds_animator_.reset(new PanelBoundsAnimation( + this, panel_.get(), animation_start_bounds_, to_bounds)); + + bounds_animator_->Start(); + last_animation_progressed_bounds_ = animation_start_bounds_; +} + +bool PanelGtk::IsAnimatingBounds() const { + return bounds_animator_.get() && bounds_animator_->is_animating(); +} + +void PanelGtk::AnimationEnded(const ui::Animation* animation) { + titlebar_->SendEnterNotifyToCloseButtonIfUnderMouse(); + panel_->manager()->OnPanelAnimationEnded(panel_.get()); +} + +void PanelGtk::AnimationProgressed(const ui::Animation* animation) { + DCHECK(is_shown_); + gfx::Rect new_bounds = bounds_animator_->CurrentValueBetween( + animation_start_bounds_, bounds_); + + gdk_window_move_resize(gtk_widget_get_window(GTK_WIDGET(window_)), + new_bounds.x(), new_bounds.y(), + new_bounds.width(), new_bounds.height()); + + last_animation_progressed_bounds_ = new_bounds; +} + +gfx::Size PanelGtk::GetNonClientFrameSize() const { + GtkAllocation window_allocation; + gtk_widget_get_allocation(window_container_, &window_allocation); + GtkAllocation contents_allocation; + gtk_widget_get_allocation(contents_expanded_, &contents_allocation); + return gfx::Size(window_allocation.width - contents_allocation.width, + window_allocation.height - contents_allocation.height); +} + +void PanelGtk::InvalidateWindow() { + GtkAllocation allocation; + gtk_widget_get_allocation(GTK_WIDGET(window_), &allocation); + gdk_window_invalidate_rect(gtk_widget_get_window(GTK_WIDGET(window_)), + &allocation, TRUE); +} + +// NativePanelTesting implementation. +class GtkNativePanelTesting : public NativePanelTesting { + public: + explicit GtkNativePanelTesting(PanelGtk* panel_gtk); + + private: + virtual void PressLeftMouseButtonTitlebar( + const gfx::Point& mouse_location, panel::ClickModifier modifier) OVERRIDE; + virtual void ReleaseMouseButtonTitlebar( + panel::ClickModifier modifier) OVERRIDE; + virtual void DragTitlebar(const gfx::Point& mouse_location) OVERRIDE; + virtual void CancelDragTitlebar() OVERRIDE; + virtual void FinishDragTitlebar() OVERRIDE; + virtual bool VerifyDrawingAttention() const OVERRIDE; + virtual bool VerifyActiveState(bool is_active) OVERRIDE; + virtual void WaitForWindowCreationToComplete() const OVERRIDE; + virtual bool IsWindowSizeKnown() const OVERRIDE; + virtual bool IsAnimatingBounds() const OVERRIDE; + virtual bool IsButtonVisible( + panel::TitlebarButtonType button_type) const OVERRIDE; + + PanelGtk* panel_gtk_; +}; + +NativePanelTesting* PanelGtk::CreateNativePanelTesting() { + return new GtkNativePanelTesting(this); +} + +GtkNativePanelTesting::GtkNativePanelTesting(PanelGtk* panel_gtk) + : panel_gtk_(panel_gtk) { +} + +void GtkNativePanelTesting::PressLeftMouseButtonTitlebar( + const gfx::Point& mouse_location, panel::ClickModifier modifier) { + // If there is an animation, wait for it to finish as we don't handle button + // clicks while animation is in progress. + while (panel_gtk_->IsAnimatingBounds()) + MessageLoopForUI::current()->RunAllPending(); + + GdkEvent* event = gdk_event_new(GDK_BUTTON_PRESS); + event->button.button = 1; + event->button.x_root = mouse_location.x(); + event->button.y_root = mouse_location.y(); + if (modifier == panel::APPLY_TO_ALL) + event->button.state |= GDK_CONTROL_MASK; + panel_gtk_->OnTitlebarButtonPressEvent( + NULL, reinterpret_cast<GdkEventButton*>(event)); + gdk_event_free(event); + MessageLoopForUI::current()->RunAllPending(); +} + +void GtkNativePanelTesting::ReleaseMouseButtonTitlebar( + panel::ClickModifier modifier) { + GdkEvent* event = gdk_event_new(GDK_BUTTON_RELEASE); + event->button.button = 1; + if (modifier == panel::APPLY_TO_ALL) + event->button.state |= GDK_CONTROL_MASK; + if (panel_gtk_->drag_helper_.get()) { + panel_gtk_->drag_helper_->OnButtonReleaseEvent( + NULL, reinterpret_cast<GdkEventButton*>(event)); + } else { + panel_gtk_->OnTitlebarButtonReleaseEvent( + NULL, reinterpret_cast<GdkEventButton*>(event)); + } + gdk_event_free(event); + MessageLoopForUI::current()->RunAllPending(); +} + +void GtkNativePanelTesting::DragTitlebar(const gfx::Point& mouse_location) { + if (!panel_gtk_->drag_helper_.get()) + return; + GdkEvent* event = gdk_event_new(GDK_MOTION_NOTIFY); + event->motion.x_root = mouse_location.x(); + event->motion.y_root = mouse_location.y(); + panel_gtk_->drag_helper_->OnMouseMoveEvent( + NULL, reinterpret_cast<GdkEventMotion*>(event)); + gdk_event_free(event); + MessageLoopForUI::current()->RunAllPending(); +} + +void GtkNativePanelTesting::CancelDragTitlebar() { + if (!panel_gtk_->drag_helper_.get()) + return; + panel_gtk_->drag_helper_->OnGrabBrokenEvent(NULL, NULL); + MessageLoopForUI::current()->RunAllPending(); +} + +void GtkNativePanelTesting::FinishDragTitlebar() { + if (!panel_gtk_->drag_helper_.get()) + return; + ReleaseMouseButtonTitlebar(panel::NO_MODIFIER); +} + +bool GtkNativePanelTesting::VerifyDrawingAttention() const { + return panel_gtk_->IsDrawingAttention(); +} + +bool GtkNativePanelTesting::VerifyActiveState(bool is_active) { + // TODO(jianli): to be implemented. http://crbug.com/102737 + return false; +} + +void GtkNativePanelTesting::WaitForWindowCreationToComplete() const { + while (g_frame_size_.IsEmpty()) + MessageLoopForUI::current()->RunAllPending(); + while (panel_gtk_->IsAnimatingBounds()) + MessageLoopForUI::current()->RunAllPending(); +} + +bool GtkNativePanelTesting::IsWindowSizeKnown() const { + return !g_frame_size_.IsEmpty(); +} + +bool GtkNativePanelTesting::IsAnimatingBounds() const { + return panel_gtk_->IsAnimatingBounds(); +} + +bool GtkNativePanelTesting::IsButtonVisible( + panel::TitlebarButtonType button_type) const { + PanelTitlebarGtk* titlebar = panel_gtk_->titlebar(); + CustomDrawButton* button; + switch (button_type) { + case panel::CLOSE_BUTTON: + button = titlebar->close_button(); + break; + case panel::MINIMIZE_BUTTON: + button = titlebar->minimize_button(); + break; + case panel::RESTORE_BUTTON: + button = titlebar->restore_button(); + break; + default: + NOTREACHED(); + return false; + } + return gtk_widget_get_visible(button->widget()); } diff --git a/chrome/browser/ui/panels/panel_gtk.h b/chrome/browser/ui/panels/panel_gtk.h new file mode 100644 index 0000000..aa80666 --- /dev/null +++ b/chrome/browser/ui/panels/panel_gtk.h @@ -0,0 +1,235 @@ +// Copyright (c) 2012 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. + +#ifndef CHROME_BROWSER_UI_PANELS_PANEL_GTK_H_ +#define CHROME_BROWSER_UI_PANELS_PANEL_GTK_H_ + +#include <gtk/gtk.h> +#include <map> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/timer.h" +#include "chrome/browser/ui/panels/native_panel.h" +#include "ui/base/animation/animation_delegate.h" +#include "ui/base/gtk/gtk_signal.h" +#include "ui/base/x/active_window_watcher_x_observer.h" +#include "ui/gfx/rect.h" + +class Panel; +class PanelBoundsAnimation; +class PanelTitlebarGtk; +class PanelDragGtk; +class GtkNativePanelTesting; + +namespace gfx { +class Image; +} + +// An implementation of the native panel in GTK. +class PanelGtk : public NativePanel, + public ui::ActiveWindowWatcherXObserver, + public ui::AnimationDelegate { + public: + enum PaintState { + PAINT_AS_ACTIVE, + PAINT_AS_INACTIVE, + PAINT_AS_MINIMIZED, + PAINT_FOR_ATTENTION + }; + + PanelGtk(Panel* panel, const gfx::Rect& bounds); + virtual ~PanelGtk(); + + void Init(); + + // Overridden from NativePanel. + virtual void ShowPanel() OVERRIDE; + virtual void ShowPanelInactive() OVERRIDE; + virtual gfx::Rect GetPanelBounds() const OVERRIDE; + virtual void SetPanelBounds(const gfx::Rect& bounds) OVERRIDE; + virtual void SetPanelBoundsInstantly(const gfx::Rect& bounds) OVERRIDE; + virtual void ClosePanel() OVERRIDE; + virtual void ActivatePanel() OVERRIDE; + virtual void DeactivatePanel() OVERRIDE; + virtual bool IsPanelActive() const OVERRIDE; + virtual void PreventActivationByOS(bool prevent_activation) OVERRIDE; + virtual gfx::NativeWindow GetNativePanelHandle() OVERRIDE; + virtual void UpdatePanelTitleBar() OVERRIDE; + virtual void UpdatePanelLoadingAnimations(bool should_animate) OVERRIDE; + virtual FindBar* CreatePanelFindBar() OVERRIDE; + virtual void NotifyPanelOnUserChangedTheme() OVERRIDE; + virtual void PanelCut() OVERRIDE; + virtual void PanelCopy() OVERRIDE; + virtual void PanelPaste() OVERRIDE; + virtual void DrawAttention(bool draw_attention) OVERRIDE; + virtual bool IsDrawingAttention() const OVERRIDE; + virtual bool PreHandlePanelKeyboardEvent( + const content::NativeWebKeyboardEvent& event, + bool* is_keyboard_shortcut) OVERRIDE; + virtual void HandlePanelKeyboardEvent( + const content::NativeWebKeyboardEvent& event) OVERRIDE; + virtual void FullScreenModeChanged(bool is_full_screen) OVERRIDE; + virtual void PanelExpansionStateChanging( + Panel::ExpansionState old_state, + Panel::ExpansionState new_state) OVERRIDE; + virtual void AttachWebContents(content::WebContents* contents) OVERRIDE; + virtual void DetachWebContents(content::WebContents* contents) OVERRIDE; + virtual Browser* GetPanelBrowser() const OVERRIDE; + // These sizes are in screen coordinates. + virtual gfx::Size WindowSizeFromContentSize( + const gfx::Size& content_size) const OVERRIDE; + virtual gfx::Size ContentSizeFromWindowSize( + const gfx::Size& window_size) const OVERRIDE; + virtual int TitleOnlyHeight() const OVERRIDE; + virtual void EnsurePanelFullyVisible() OVERRIDE; + virtual void SetPanelAlwaysOnTop(bool on_top) OVERRIDE; + virtual void EnableResizeByMouse(bool enable) OVERRIDE; + virtual void UpdatePanelMinimizeRestoreButtonVisibility() OVERRIDE; + + virtual NativePanelTesting* CreateNativePanelTesting() OVERRIDE; + + // Overridden from ActiveWindowWatcherXObserver. + virtual void ActiveWindowChanged(GdkWindow* active_window) OVERRIDE; + + bool UsingDefaultTheme() const; + + Panel* panel() const { return panel_.get(); } + PaintState paint_state() const { return paint_state_; } + PanelTitlebarGtk* titlebar() const { return titlebar_.get(); } + + private: + friend class GtkNativePanelTesting; + + // Applies our custom window shape with rounded top corners. + void UpdateWindowShape(int width, int height); + + // Checks to see if the mouse pointer at |x|, |y| is over the border of the + // custom frame (a spot that should trigger a window resize). Returns true if + // it should and sets |edge|. + bool GetWindowEdge(int x, int y, GdkWindowEdge* edge) const; + + // Connect/disconnect accelerators for keyboard shortcut support. + void ConnectAccelerators(); + void DisconnectAccelerators(); + + // Returns the image to paint the frame. + const gfx::Image* GetFrameBackground() const; + const gfx::Image* GetDefaultFrameBackground() const; + const gfx::Image* GetThemedFrameBackground() const; + + // Animation when panel is first shown. + void RevealPanel(); + + void StartBoundsAnimation(const gfx::Rect& from_bounds, + const gfx::Rect& to_bounds); + bool IsAnimatingBounds() const; + + // Overridden from AnimationDelegate: + virtual void AnimationEnded(const ui::Animation* animation) OVERRIDE; + virtual void AnimationProgressed(const ui::Animation* animation) OVERRIDE; + + // Creates helper for handling drags if not already created. + void EnsureDragHelperCreated(); + + void SetBoundsInternal(const gfx::Rect& bounds, bool animate); + + void LoadingAnimationCallback(); + + // Returns the size of the window frame around the client content area. + gfx::Size GetNonClientFrameSize() const; + + // Invalidate window to force repaint. + void InvalidateWindow(); + + // Callback for accelerator activation. |user_data| stores the command id + // of the matched accelerator. + static gboolean OnGtkAccelerator(GtkAccelGroup* accel_group, + GObject* acceleratable, + guint keyval, + GdkModifierType modifier, + void* user_data); + + CHROMEGTK_CALLBACK_1(PanelGtk, gboolean, OnMainWindowDeleteEvent, + GdkEvent*); + CHROMEGTK_CALLBACK_0(PanelGtk, void, OnMainWindowDestroy); + CHROMEGTK_CALLBACK_1(PanelGtk, gboolean, OnConfigure, GdkEventConfigure*); + // Callback for when the custom frame alignment needs to be redrawn. + CHROMEGTK_CALLBACK_1(PanelGtk, gboolean, OnCustomFrameExpose, + GdkEventExpose*); + // Key press event callback. + CHROMEGTK_CALLBACK_1(PanelGtk, gboolean, OnKeyPress, GdkEventKey*); + // Mouse move and button press callbacks. If mouse hits titlebar, + // the titlebar gets the event, else the window gets the button press. + CHROMEGTK_CALLBACK_1(PanelGtk, gboolean, OnMouseMoveEvent, + GdkEventMotion*); + CHROMEGTK_CALLBACK_1(PanelGtk, gboolean, OnButtonPressEvent, + GdkEventButton*); + CHROMEGTK_CALLBACK_1(PanelGtk, gboolean, + OnTitlebarButtonPressEvent, GdkEventButton*); + CHROMEGTK_CALLBACK_1(PanelGtk, gboolean, + OnTitlebarButtonReleaseEvent, GdkEventButton*); + + scoped_ptr<Panel> panel_; + gfx::Rect bounds_; + + // True after panel has been shown. + bool is_shown_; + + scoped_ptr<PanelDragGtk> drag_helper_; + + // The configure size of the current window, used to figure out whether to + // ignore later configure events. See OnConfigure() for more information. + gfx::Size configure_size_; + + // Indicates different painting state, active, drawing attention or else. + PaintState paint_state_; + + // Indicates that the panel is currently drawing attention. + bool is_drawing_attention_; + + // Used to animate the bounds change. + scoped_ptr<PanelBoundsAnimation> bounds_animator_; + gfx::Rect animation_start_bounds_; + + // This records the bounds set on the last animation progress notification. + // We need this for the case where a new bounds animation starts before the + // current one completes. In this case, we want to start the new animation + // from where the last one left. + gfx::Rect last_animation_progressed_bounds_; + + // The timer used to update frames for the Loading Animation. + base::RepeatingTimer<PanelGtk> loading_animation_timer_; + + // The current window cursor. We set it to a resize cursor when over the + // custom frame border. We set it to NULL if we want the default cursor. + GdkCursor* frame_cursor_; + + // True if the window manager thinks the window is active. Not all window + // managers keep track of this state (_NET_ACTIVE_WINDOW), in which case + // this will always be true. + bool is_active_; + + // Top level window. + GtkWindow* window_; + // GtkAlignment that holds the interior components of the chromium window. + // This is used to draw the custom frame border and content shadow. + GtkWidget* window_container_; + // VBox that holds everything (titlebar, web contents). + GtkWidget* window_vbox_; + // EventBox that holds web contents. + GtkWidget* render_area_event_box_; + // We insert and remove WebContents GtkWidgets into this expanded. + GtkWidget* contents_expanded_; + + // The accelerator group used to handle accelerators, owned by this object. + GtkAccelGroup* accel_group_; + + // The container for the titlebar. + scoped_ptr<PanelTitlebarGtk> titlebar_; + + DISALLOW_COPY_AND_ASSIGN(PanelGtk); +}; + +#endif // CHROME_BROWSER_UI_PANELS_PANEL_GTK_H_ diff --git a/chrome/browser/ui/panels/panel_host.cc b/chrome/browser/ui/panels/panel_host.cc index a4ec274..3b425c4 100644 --- a/chrome/browser/ui/panels/panel_host.cc +++ b/chrome/browser/ui/panels/panel_host.cc @@ -11,6 +11,7 @@ #include "chrome/browser/favicon/favicon_tab_helper.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/panels/panel.h" +#include "chrome/browser/ui/prefs/prefs_tab_helper.h" #include "chrome/browser/view_type_utils.h" #include "chrome/common/chrome_notification_types.h" #include "chrome/common/extensions/extension_messages.h" @@ -53,14 +54,16 @@ void PanelHost::Init(const GURL& url) { content::WebContentsObserver::Observe(web_contents_.get()); favicon_tab_helper_.reset(new FaviconTabHelper(web_contents_.get())); + prefs_tab_helper_.reset(new PrefsTabHelper(web_contents_.get())); web_contents_->GetController().LoadURL( url, content::Referrer(), content::PAGE_TRANSITION_LINK, std::string()); } void PanelHost::DestroyWebContents() { - web_contents_.reset(); favicon_tab_helper_.reset(); + prefs_tab_helper_.reset(); + web_contents_.reset(); } SkBitmap PanelHost::GetPageIcon() const { diff --git a/chrome/browser/ui/panels/panel_host.h b/chrome/browser/ui/panels/panel_host.h index fa68c3d..f2732c2 100644 --- a/chrome/browser/ui/panels/panel_host.h +++ b/chrome/browser/ui/panels/panel_host.h @@ -15,6 +15,7 @@ class FaviconTabHelper; class GURL; class Panel; +class PrefsTabHelper; class Profile; namespace content { @@ -98,6 +99,7 @@ class PanelHost : public content::WebContentsDelegate, // The following factory is used to close the panel via the message loop. base::WeakPtrFactory<PanelHost> weak_factory_; + scoped_ptr<PrefsTabHelper> prefs_tab_helper_; scoped_ptr<FaviconTabHelper> favicon_tab_helper_; scoped_ptr<content::WebContents> web_contents_; diff --git a/chrome/browser/ui/panels/panel_resize_browsertest.cc b/chrome/browser/ui/panels/panel_resize_browsertest.cc index 41c026e..7ca7ea3 100644 --- a/chrome/browser/ui/panels/panel_resize_browsertest.cc +++ b/chrome/browser/ui/panels/panel_resize_browsertest.cc @@ -8,9 +8,6 @@ #include "chrome/browser/ui/panels/panel_manager.h" #include "chrome/browser/ui/panels/panel_resize_controller.h" -// Refactor has only been done for Win and Mac panels so far. -#if defined(OS_WIN) || defined(OS_MACOSX) - class PanelResizeBrowserTest : public BasePanelBrowserTest { public: PanelResizeBrowserTest() : BasePanelBrowserTest() { @@ -413,5 +410,3 @@ IN_PROC_BROWSER_TEST_F(PanelResizeBrowserTest, ResizeDetachedPanelToTop) { panel_manager->CloseAll(); } - -#endif // OS_WIN || OS_MACOSX diff --git a/chrome/browser/ui/panels/panel_titlebar_gtk.cc b/chrome/browser/ui/panels/panel_titlebar_gtk.cc new file mode 100644 index 0000000..e15f9c6 --- /dev/null +++ b/chrome/browser/ui/panels/panel_titlebar_gtk.cc @@ -0,0 +1,311 @@ +// Copyright (c) 2012 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/ui/panels/panel_titlebar_gtk.h" + +#include "base/utf_string_conversions.h" +#include "chrome/browser/ui/gtk/custom_button.h" +#include "chrome/browser/ui/gtk/gtk_theme_service.h" +#include "chrome/browser/ui/gtk/gtk_util.h" +#include "chrome/browser/ui/panels/panel.h" +#include "chrome/browser/ui/panels/panel_gtk.h" +#include "content/public/browser/web_contents.h" +#include "grit/generated_resources.h" +#include "grit/theme_resources.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/base/gtk/gtk_compat.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/gfx/skia_utils_gtk.h" + +namespace { + +// Padding around the titlebar. +const int kPanelTitlebarPaddingTop = 7; +const int kPanelTitlebarPaddingBottom = 7; +const int kPanelTitlebarPaddingLeft = 4; +const int kPanelTitlebarPaddingRight = 8; + +// Spacing between buttons of panel's titlebar. +const int kPanelButtonSpacing = 5; + +// Spacing between the icon and the title text. +const int kPanelIconTitleSpacing = 9; + +// Color used to draw title text under default theme. +const SkColor kTitleTextDefaultColor = SkColorSetRGB(0xf9, 0xf9, 0xf9); + +// Markup used to paint the title with the desired font. +const char* const kTitleMarkupPrefix = "<span face='Arial' size='11264'>"; +const char* const kTitleMarkupSuffix = "</span>"; + +} // namespace + +PanelTitlebarGtk::PanelTitlebarGtk(PanelGtk* panel_gtk) + : panel_gtk_(panel_gtk), + container_(NULL), + titlebar_right_buttons_vbox_(NULL), + titlebar_right_buttons_hbox_(NULL), + icon_(NULL), + title_(NULL), + theme_service_(GtkThemeService::GetFrom(panel_gtk_->panel()->profile())) { +} + +PanelTitlebarGtk::~PanelTitlebarGtk() { +} + +void PanelTitlebarGtk::Init() { + container_ = gtk_event_box_new(); + gtk_widget_set_name(container_, "chrome-panel-titlebar"); + gtk_event_box_set_visible_window(GTK_EVENT_BOX(container_), FALSE); + + // We use an alignment to control the titlebar paddings. + GtkWidget* container_alignment = gtk_alignment_new(0.0, 0.0, 1.0, 1.0); + gtk_container_add(GTK_CONTAINER(container_), container_alignment); + gtk_alignment_set_padding(GTK_ALIGNMENT(container_alignment), + kPanelTitlebarPaddingTop, + kPanelTitlebarPaddingBottom, + kPanelTitlebarPaddingLeft, + kPanelTitlebarPaddingRight); + + // Add a container box. + GtkWidget* container_hbox = gtk_hbox_new(FALSE, 0); + gtk_container_add(GTK_CONTAINER(container_alignment), container_hbox); + + // Add minimize/restore and close buttons. Panel buttons are always placed + // on the right part of the titlebar. + titlebar_right_buttons_vbox_ = gtk_vbox_new(FALSE, 0); + gtk_box_pack_end(GTK_BOX(container_hbox), titlebar_right_buttons_vbox_, + FALSE, FALSE, 0); + BuildButtons(); + + // Add hbox for holding icon and title. + GtkWidget* icon_title_hbox = gtk_hbox_new(FALSE, kPanelIconTitleSpacing); + gtk_box_pack_start(GTK_BOX(container_hbox), icon_title_hbox, TRUE, TRUE, 0); + + // Add icon. We use the app logo as a placeholder image so the title doesn't + // jump around. + ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); + icon_ = gtk_image_new_from_pixbuf(rb.GetNativeImageNamed( + IDR_PRODUCT_LOGO_16, ui::ResourceBundle::RTL_ENABLED).ToGdkPixbuf()); + g_object_set_data(G_OBJECT(icon_), "left-align-popup", + reinterpret_cast<void*>(true)); + gtk_box_pack_start(GTK_BOX(icon_title_hbox), icon_, FALSE, FALSE, 0); + + // Add title. + title_ = gtk_label_new(NULL); + gtk_label_set_ellipsize(GTK_LABEL(title_), PANGO_ELLIPSIZE_END); + gtk_misc_set_alignment(GTK_MISC(title_), 0.0, 0.5); + gtk_box_pack_start(GTK_BOX(icon_title_hbox), title_, TRUE, TRUE, 0); + UpdateTitleAndIcon(); + UpdateTextColor(); + + gtk_widget_show_all(container_); +} + +SkColor PanelTitlebarGtk::GetTextColor() const { + if (panel_gtk_->UsingDefaultTheme()) + return kTitleTextDefaultColor; + return theme_service_->GetColor(panel_gtk_->paint_state() == + PanelGtk::PAINT_AS_ACTIVE ? + ThemeService::COLOR_TAB_TEXT : + ThemeService::COLOR_BACKGROUND_TAB_TEXT); +} + +void PanelTitlebarGtk::BuildButtons() { + minimize_button_.reset(CreateButton(panel::MINIMIZE_BUTTON)); + restore_button_.reset(CreateButton(panel::RESTORE_BUTTON)); + close_button_.reset(CreateButton(panel::CLOSE_BUTTON)); + + // We control visibility of minimize and restore buttons. + gtk_widget_set_no_show_all(minimize_button_->widget(), TRUE); + gtk_widget_set_no_show_all(restore_button_->widget(), TRUE); + + // Now show the correct widgets in the two hierarchies. + UpdateMinimizeRestoreButtonVisibility(); +} + +CustomDrawButton* PanelTitlebarGtk::CreateButton( + panel::TitlebarButtonType button_type) { + int normal_image_id = -1; + int pressed_image_id = -1; + int hover_image_id = -1; + int tooltip_id = -1; + GetButtonResources(button_type, &normal_image_id, &pressed_image_id, + &hover_image_id, &tooltip_id); + + CustomDrawButton* button = new CustomDrawButton(normal_image_id, + pressed_image_id, + hover_image_id, + 0); + gtk_widget_add_events(GTK_WIDGET(button->widget()), GDK_POINTER_MOTION_MASK); + g_signal_connect(button->widget(), "clicked", + G_CALLBACK(OnButtonClickedThunk), this); + + std::string localized_tooltip = l10n_util::GetStringUTF8(tooltip_id); + gtk_widget_set_tooltip_text(button->widget(), + localized_tooltip.c_str()); + + GtkWidget* box = GetButtonHBox(); + gtk_box_pack_start(GTK_BOX(box), button->widget(), FALSE, FALSE, 0); + return button; +} + +void PanelTitlebarGtk::GetButtonResources( + panel::TitlebarButtonType button_type, + int* normal_image_id, + int* pressed_image_id, + int* hover_image_id, + int* tooltip_id) const { + switch (button_type) { + case panel::CLOSE_BUTTON: + *normal_image_id = IDR_PANEL_CLOSE; + *pressed_image_id = IDR_PANEL_CLOSE_C; + *hover_image_id = IDR_PANEL_CLOSE_H; + *tooltip_id = IDS_PANEL_CLOSE_TOOLTIP; + break; + case panel::MINIMIZE_BUTTON: + *normal_image_id = IDR_PANEL_MINIMIZE; + *pressed_image_id = IDR_PANEL_MINIMIZE_C; + *hover_image_id = IDR_PANEL_MINIMIZE_H; + *tooltip_id = IDS_PANEL_MINIMIZE_TOOLTIP; + break; + case panel::RESTORE_BUTTON: + *normal_image_id = IDR_PANEL_RESTORE; + *pressed_image_id = IDR_PANEL_RESTORE_C; + *hover_image_id = IDR_PANEL_RESTORE_H; + *tooltip_id = IDS_PANEL_RESTORE_TOOLTIP; + break; + } +} + +GtkWidget* PanelTitlebarGtk::GetButtonHBox() { + if (!titlebar_right_buttons_hbox_) { + // We put the minimize/restore/close buttons in a vbox so they are top + // aligned (up to padding) and don't vertically stretch. + titlebar_right_buttons_hbox_ = gtk_hbox_new(FALSE, kPanelButtonSpacing); + gtk_box_pack_start(GTK_BOX(titlebar_right_buttons_vbox_), + titlebar_right_buttons_hbox_, FALSE, FALSE, 0); + } + + return titlebar_right_buttons_hbox_; +} + +void PanelTitlebarGtk::UpdateTitleAndIcon() { + std::string title_text = + UTF16ToUTF8(panel_gtk_->panel()->GetWindowTitle()); + + // Add the markup to show the title in the desired font. + gchar* escaped_title_text = g_markup_escape_text(title_text.c_str(), -1); + gchar* title_text_with_markup = g_strconcat(kTitleMarkupPrefix, + escaped_title_text, + kTitleMarkupSuffix, + NULL); + gtk_label_set_markup(GTK_LABEL(title_), title_text_with_markup); + g_free(escaped_title_text); + g_free(title_text_with_markup); +} + +void PanelTitlebarGtk::UpdateThrobber( + content::WebContents* web_contents) { + if (web_contents && web_contents->IsLoading()) { + GdkPixbuf* icon_pixbuf = + throbber_.GetNextFrame(web_contents->IsWaitingForResponse()); + gtk_image_set_from_pixbuf(GTK_IMAGE(icon_), icon_pixbuf); + } else { + ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); + + SkBitmap icon = panel_gtk_->panel()->GetCurrentPageIcon(); + if (icon.empty()) { + // Fallback to the Chromium icon if the page has no icon. + gtk_image_set_from_pixbuf(GTK_IMAGE(icon_), + rb.GetNativeImageNamed(IDR_PRODUCT_LOGO_16).ToGdkPixbuf()); + } else { + GdkPixbuf* icon_pixbuf = gfx::GdkPixbufFromSkBitmap(icon); + gtk_image_set_from_pixbuf(GTK_IMAGE(icon_), icon_pixbuf); + g_object_unref(icon_pixbuf); + } + + throbber_.Reset(); + } +} + +void PanelTitlebarGtk::UpdateTextColor() { + GdkColor text_color = gfx::SkColorToGdkColor(GetTextColor()); + gtk_util::SetLabelColor(title_, &text_color); +} + +void PanelTitlebarGtk::UpdateMinimizeRestoreButtonVisibility() { + Panel* panel = panel_gtk_->panel(); + gtk_widget_set_visible(minimize_button_->widget(), panel->CanMinimize()); + gtk_widget_set_visible(restore_button_->widget(), panel->CanRestore()); +} + +void PanelTitlebarGtk::OnButtonClicked(GtkWidget* button) { + Panel* panel = panel_gtk_->panel(); + if (close_button_->widget() == button) { + panel->Close(); + return; + } + + GdkEvent* event = gtk_get_current_event(); + DCHECK(event && event->type == GDK_BUTTON_RELEASE); + + if (minimize_button_->widget() == button) { + panel->OnMinimizeButtonClicked( + (event->button.state & GDK_CONTROL_MASK) ? + panel::APPLY_TO_ALL : panel::NO_MODIFIER); + } else if (restore_button_->widget() == button) { + panel->OnRestoreButtonClicked( + (event->button.state & GDK_CONTROL_MASK) ? + panel::APPLY_TO_ALL : panel::NO_MODIFIER); + } + + gdk_event_free(event); +} + +void PanelTitlebarGtk::SendEnterNotifyToCloseButtonIfUnderMouse() { + if (!close_button()) + return; + + gint x; + gint y; + GtkAllocation widget_allocation = close_button()->WidgetAllocation(); + gtk_widget_get_pointer(GTK_WIDGET(close_button()->widget()), &x, &y); + + gfx::Rect button_rect(0, 0, widget_allocation.width, + widget_allocation.height); + if (!button_rect.Contains(x, y)) { + // Mouse is not over the close button. + return; + } + + // Create and emit an enter-notify-event on close button. + GValue return_value; + return_value.g_type = G_TYPE_BOOLEAN; + g_value_set_boolean(&return_value, false); + + GdkEvent* event = gdk_event_new(GDK_ENTER_NOTIFY); + event->crossing.window = + gtk_button_get_event_window(GTK_BUTTON(close_button()->widget())); + event->crossing.send_event = FALSE; + event->crossing.subwindow = gtk_widget_get_window(close_button()->widget()); + event->crossing.time = gtk_util::XTimeNow(); + event->crossing.x = x; + event->crossing.y = y; + event->crossing.x_root = widget_allocation.x; + event->crossing.y_root = widget_allocation.y; + event->crossing.mode = GDK_CROSSING_NORMAL; + event->crossing.detail = GDK_NOTIFY_ANCESTOR; + event->crossing.focus = true; + event->crossing.state = 0; + + g_signal_emit_by_name(GTK_OBJECT(close_button()->widget()), + "enter-notify-event", event, + &return_value); +} + +GtkWidget* PanelTitlebarGtk::widget() const { + return container_; +} diff --git a/chrome/browser/ui/panels/panel_titlebar_gtk.h b/chrome/browser/ui/panels/panel_titlebar_gtk.h new file mode 100644 index 0000000..ddfa452 --- /dev/null +++ b/chrome/browser/ui/panels/panel_titlebar_gtk.h @@ -0,0 +1,98 @@ +// Copyright (c) 2012 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. + +#ifndef CHROME_BROWSER_UI_PANELS_PANEL_TITLEBAR_GTK_H_ +#define CHROME_BROWSER_UI_PANELS_PANEL_TITLEBAR_GTK_H_ + +#include <gtk/gtk.h> + +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "chrome/browser/ui/gtk/titlebar_throb_animation.h" +#include "chrome/browser/ui/panels/panel_constants.h" +#include "ui/base/gtk/gtk_signal.h" +#include "ui/gfx/skia_util.h" + +class CustomDrawButton; +class GtkThemeService; +class PanelGtk; + +namespace content { +class WebContents; +} + +class PanelTitlebarGtk { + public: + explicit PanelTitlebarGtk(PanelGtk* panel_gtk); + virtual ~PanelTitlebarGtk(); + + void UpdateTextColor(); + void UpdateMinimizeRestoreButtonVisibility(); + + // When a panel appears in the same position as the one of the panel being + // closed and the cursor stays in the close button, the close button appears + // not to be clickable. This is because neither "enter-notify-event" nor + // "clicked" event for the new panel gets fired if the mouse does not move. + // This creates a bad experience when a user has multiple panels of the same + // size (which is typical) and tries closing them all by repeatedly clicking + // in the same place on the screen. + // + // Opened a gtk bug for this - + // https://bugzilla.gnome.org/show_bug.cgi?id=667841 + void SendEnterNotifyToCloseButtonIfUnderMouse(); + + void Init(); + void UpdateTitleAndIcon(); + void UpdateThrobber(content::WebContents* web_contents); + GtkWidget* widget() const; + + private: + friend class GtkNativePanelTesting; + + void BuildButtons(); + CustomDrawButton* CreateButton(panel::TitlebarButtonType button_type); + void GetButtonResources(panel::TitlebarButtonType button_type, + int* normal_image_id, + int* pressed_image_id, + int* hover_image_id, + int* tooltip_id) const; + GtkWidget* GetButtonHBox(); + + // Callback for minimize/restore/close buttons. + CHROMEGTK_CALLBACK_0(PanelTitlebarGtk, void, OnButtonClicked); + + CustomDrawButton* close_button() const { return close_button_.get(); } + CustomDrawButton* minimize_button() const { return minimize_button_.get(); } + CustomDrawButton* restore_button() const { return restore_button_.get(); } + + SkColor GetTextColor() const; + + // Pointers to the native panel window that owns us and its GtkWindow. + PanelGtk* panel_gtk_; + + // The container widget the holds the hbox which contains the whole titlebar. + GtkWidget* container_; + + // VBoxes that holds the minimize/restore/close buttons box. + GtkWidget* titlebar_right_buttons_vbox_; + + // HBoxes that contains the actual min/max/close buttons. + GtkWidget* titlebar_right_buttons_hbox_; + + // The icon and page title. + GtkWidget* icon_; + GtkWidget* title_; + + // The buttons. + scoped_ptr<CustomDrawButton> close_button_; + scoped_ptr<CustomDrawButton> minimize_button_; + scoped_ptr<CustomDrawButton> restore_button_; + + TitlebarThrobAnimation throbber_; + GtkThemeService* theme_service_; + + DISALLOW_COPY_AND_ASSIGN(PanelTitlebarGtk); +}; + +#endif // CHROME_BROWSER_UI_PANELS_PANEL_TITLEBAR_GTK_H_ diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 2936b55..c573a24 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -3205,6 +3205,8 @@ 'browser/ui/gtk/gtk_tree.h', 'browser/ui/gtk/gtk_util.cc', 'browser/ui/gtk/gtk_util.h', + 'browser/ui/gtk/gtk_window_util.cc', + 'browser/ui/gtk/gtk_window_util.h', 'browser/ui/gtk/hover_controller_gtk.cc', 'browser/ui/gtk/hover_controller_gtk.h', 'browser/ui/gtk/hung_renderer_dialog_gtk.cc', @@ -3398,6 +3400,7 @@ 'browser/ui/panels/panel_frame_view.cc', 'browser/ui/panels/panel_frame_view.h', 'browser/ui/panels/panel_gtk.cc', + 'browser/ui/panels/panel_gtk.h', 'browser/ui/panels/panel_host.cc', 'browser/ui/panels/panel_host.h', 'browser/ui/panels/panel_resize_controller.cc', @@ -3410,6 +3413,8 @@ 'browser/ui/panels/panel_mouse_watcher_timer.cc', 'browser/ui/panels/panel_strip.cc', 'browser/ui/panels/panel_strip.h', + 'browser/ui/panels/panel_titlebar_gtk.cc', + 'browser/ui/panels/panel_titlebar_gtk.h', 'browser/ui/panels/panel_titlebar_view_cocoa.h', 'browser/ui/panels/panel_titlebar_view_cocoa.mm', 'browser/ui/panels/panel_utils_cocoa.h', |