summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjennb@chromium.org <jennb@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-08-13 21:27:06 +0000
committerjennb@chromium.org <jennb@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-08-13 21:27:06 +0000
commit052125aac0a6238540f72c134c0091f7e8c26eea (patch)
treeaa131d15a1dfb7b5a7bd8367032781a8014b6ba3
parentfe476b031d96e1cc92321c57e2da967fa510407d (diff)
downloadchromium_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
-rw-r--r--chrome/browser/ui/gtk/browser_window_gtk.cc59
-rw-r--r--chrome/browser/ui/gtk/gtk_util.cc50
-rw-r--r--chrome/browser/ui/gtk/gtk_util.h5
-rw-r--r--chrome/browser/ui/gtk/gtk_window_util.cc97
-rw-r--r--chrome/browser/ui/gtk/gtk_window_util.h32
-rw-r--r--chrome/browser/ui/panels/detached_panel_browsertest.cc5
-rw-r--r--chrome/browser/ui/panels/docked_panel_browsertest.cc5
-rw-r--r--chrome/browser/ui/panels/panel.cc4
-rw-r--r--chrome/browser/ui/panels/panel.h2
-rw-r--r--chrome/browser/ui/panels/panel_and_desktop_notification_browsertest.cc6
-rw-r--r--chrome/browser/ui/panels/panel_browsertest.cc11
-rw-r--r--chrome/browser/ui/panels/panel_drag_browsertest.cc5
-rw-r--r--chrome/browser/ui/panels/panel_drag_gtk.h3
-rw-r--r--chrome/browser/ui/panels/panel_gtk.cc1173
-rw-r--r--chrome/browser/ui/panels/panel_gtk.h235
-rw-r--r--chrome/browser/ui/panels/panel_host.cc5
-rw-r--r--chrome/browser/ui/panels/panel_host.h2
-rw-r--r--chrome/browser/ui/panels/panel_resize_browsertest.cc5
-rw-r--r--chrome/browser/ui/panels/panel_titlebar_gtk.cc311
-rw-r--r--chrome/browser/ui/panels/panel_titlebar_gtk.h98
-rw-r--r--chrome/chrome_browser.gypi5
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',