diff options
-rw-r--r-- | chrome/browser/gtk/dnd_registry.h | 6 | ||||
-rw-r--r-- | chrome/browser/gtk/tabs/dragged_tab_gtk.cc | 8 | ||||
-rw-r--r-- | chrome/browser/gtk/tabs/tab_strip_gtk.cc | 311 | ||||
-rw-r--r-- | chrome/browser/gtk/tabs/tab_strip_gtk.h | 90 | ||||
-rw-r--r-- | chrome/common/gtk_util.cc | 13 | ||||
-rw-r--r-- | chrome/common/gtk_util.h | 6 |
6 files changed, 426 insertions, 8 deletions
diff --git a/chrome/browser/gtk/dnd_registry.h b/chrome/browser/gtk/dnd_registry.h index 1500c44..141e33a 100644 --- a/chrome/browser/gtk/dnd_registry.h +++ b/chrome/browser/gtk/dnd_registry.h @@ -16,6 +16,11 @@ enum { // Tab DND items: X_CHROME_TAB = 0, + // Tabstrip DND items: + X_CHROME_STRING, + X_CHROME_TEXT_PLAIN, + X_CHROME_TEXT_URI_LIST, + // Bookmark DND items: X_CHROME_BOOKMARK_ITEM }; @@ -23,4 +28,3 @@ enum { }; #endif // CHROME_BROWSER_GTK_DND_REGISTRY_H_ - diff --git a/chrome/browser/gtk/tabs/dragged_tab_gtk.cc b/chrome/browser/gtk/tabs/dragged_tab_gtk.cc index 48413ef..b7ca602 100644 --- a/chrome/browser/gtk/tabs/dragged_tab_gtk.cc +++ b/chrome/browser/gtk/tabs/dragged_tab_gtk.cc @@ -11,6 +11,7 @@ #include "chrome/browser/tab_contents/tab_contents.h" #include "chrome/browser/tabs/tab_strip_model.h" #include "chrome/browser/gtk/tabs/tab_renderer_gtk.h" +#include "chrome/common/gtk_util.h" #include "third_party/skia/include/core/SkShader.h" namespace { @@ -24,11 +25,6 @@ const float kScalingFactor = 0.5; const int kAnimateToBoundsDurationMs = 150; -bool IsScreenComposited() { - GdkScreen* screen = gdk_screen_get_default(); - return gdk_screen_is_composited(screen) == TRUE; -} - } // namespace //////////////////////////////////////////////////////////////////////////////// @@ -215,7 +211,7 @@ void DraggedTabGtk::SetContainerShapeMask() { gboolean DraggedTabGtk::OnExposeEvent(GtkWidget* widget, GdkEventExpose* event, DraggedTabGtk* dragged_tab) { - if (IsScreenComposited()) { + if (gtk_util::IsScreenComposited()) { dragged_tab->SetContainerTransparency(); } else { dragged_tab->SetContainerShapeMask(); diff --git a/chrome/browser/gtk/tabs/tab_strip_gtk.cc b/chrome/browser/gtk/tabs/tab_strip_gtk.cc index 7cbdf67..38f610b 100644 --- a/chrome/browser/gtk/tabs/tab_strip_gtk.cc +++ b/chrome/browser/gtk/tabs/tab_strip_gtk.cc @@ -13,9 +13,11 @@ #include "chrome/browser/browser.h" #include "chrome/browser/browser_theme_provider.h" #include "chrome/browser/gtk/custom_button.h" +#include "chrome/browser/gtk/dnd_registry.h" #include "chrome/browser/gtk/tabs/dragged_tab_controller_gtk.h" #include "chrome/browser/profile.h" #include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/common/gtk_util.h" #include "chrome/common/pref_names.h" #include "chrome/common/pref_service.h" #include "grit/app_resources.h" @@ -41,6 +43,10 @@ const int kTabHOffset = -16; // A linux specific menu item for toggling window decorations. const int kShowWindowDecorationsCommand = 200; +// Size of the drop indicator. +static int drop_indicator_width; +static int drop_indicator_height; + inline int Round(double x) { return static_cast<int>(x + 0.5); } @@ -53,6 +59,20 @@ gfx::Rect GetInitialWidgetBounds(GtkWidget* widget) { return gfx::Rect(0, 0, request.width, request.height); } +// Mime types for DnD. Used to synchronize across applications. +const char kTargetString[] = "STRING"; +const char kTargetTextPlain[] = "text/plain"; +const char kTargetTextUriList[] = "text/uri-list"; + +// Table of the mime types that we accept with their options. +const GtkTargetEntry kTargetTable[] = { + { const_cast<gchar*>(kTargetString), 0, dnd::X_CHROME_STRING }, + { const_cast<gchar*>(kTargetTextPlain), 0, dnd::X_CHROME_TEXT_PLAIN }, + { const_cast<gchar*>(kTargetTextUriList), 0, dnd::X_CHROME_TEXT_URI_LIST } +}; + +const int kTargetTableSize = G_N_ELEMENTS(kTargetTable); + } // namespace //////////////////////////////////////////////////////////////////////////////// @@ -359,7 +379,7 @@ class MoveTabAnimation : public TabStripGtk::TabAnimation { } protected: - // Overridden from TabStrip::TabAnimation: + // Overridden from TabStripGtk::TabAnimation: virtual int GetDuration() const { return kReorderAnimationDurationMs; } private: @@ -469,18 +489,35 @@ void TabStripGtk::Init(int width, Profile* profile) { gtk_widget_set_size_request(tabstrip_.get(), width, TabGtk::GetMinimumUnselectedSize().height()); gtk_widget_set_app_paintable(tabstrip_.get(), TRUE); + gtk_drag_dest_set(tabstrip_.get(), GTK_DEST_DEFAULT_ALL, + kTargetTable, kTargetTableSize, + static_cast<GdkDragAction>( + GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK)); g_signal_connect(G_OBJECT(tabstrip_.get()), "expose-event", G_CALLBACK(OnExpose), this); g_signal_connect(G_OBJECT(tabstrip_.get()), "size-allocate", G_CALLBACK(OnSizeAllocate), this); g_signal_connect(G_OBJECT(tabstrip_.get()), "button-press-event", G_CALLBACK(OnButtonPress), this); + g_signal_connect(G_OBJECT(tabstrip_.get()), "drag-motion", + G_CALLBACK(OnDragMotion), this); + g_signal_connect(G_OBJECT(tabstrip_.get()), "drag-drop", + G_CALLBACK(OnDragDrop), this); + g_signal_connect(G_OBJECT(tabstrip_.get()), "drag-data-received", + G_CALLBACK(OnDragDataReceived), this); newtab_button_.reset(MakeNewTabButton()); gtk_widget_show_all(tabstrip_.get()); bounds_ = GetInitialWidgetBounds(tabstrip_.get()); + + if (drop_indicator_width == 0) { + // Direction doesn't matter, both images are the same size. + GdkPixbuf* drop_image = GetDropArrowImage(true); + drop_indicator_width = gdk_pixbuf_get_width(drop_image); + drop_indicator_height = gdk_pixbuf_get_height(drop_image); + } } void TabStripGtk::AddTabStripToBox(GtkWidget* box) { @@ -974,6 +1011,231 @@ void TabStripGtk::ResizeLayoutTabs() { StartResizeLayoutAnimation(); } +gfx::Rect TabStripGtk::GetDropBounds(int drop_index, + bool drop_before, + bool* is_beneath) { + DCHECK(drop_index != -1); + int center_x; + if (drop_index < GetTabCount()) { + TabGtk* tab = GetTabAt(drop_index); + if (drop_before) + center_x = tab->x() - (kTabHOffset / 2); + else + center_x = tab->x() + (tab->width() / 2); + } else { + TabGtk* last_tab = GetTabAt(drop_index - 1); + center_x = last_tab->x() + last_tab->width() + (kTabHOffset / 2); + } + + // TODO(jhawkins): Handle RTL layout. + + // Determine the screen bounds. + gfx::Point drop_loc(center_x - drop_indicator_width / 2, + -drop_indicator_height); + gtk_util::ConvertWidgetPointToScreen(tabstrip_.get(), &drop_loc); + gfx::Rect drop_bounds(drop_loc.x(), drop_loc.y(), drop_indicator_width, + drop_indicator_height); + + // TODO(jhawkins): We always display the arrow underneath the tab because we + // don't have custom frame support yet. + *is_beneath = true; + if (*is_beneath) + drop_bounds.Offset(0, drop_bounds.height() + bounds().height()); + + return drop_bounds; +} + +void TabStripGtk::UpdateDropIndex(GdkDragContext* context, gint x, gint y) { + // TODO(jhawkins): Handle RTL layout. + for (int i = 0; i < GetTabCount(); ++i) { + TabGtk* tab = GetTabAt(i); + const int tab_max_x = tab->x() + tab->width(); + const int hot_width = tab->width() / 3; + if (x < tab_max_x) { + if (x < tab->x() + hot_width) + SetDropIndex(i, true); + else if (x >= tab_max_x - hot_width) + SetDropIndex(i + 1, true); + else + SetDropIndex(i, false); + return; + } + } + + // The drop isn't over a tab, add it to the end. + SetDropIndex(GetTabCount(), true); +} + +void TabStripGtk::SetDropIndex(int index, bool drop_before) { + if (index == -1) { + if (drop_info_.get()) + drop_info_.reset(NULL); + return; + } + + if (drop_info_.get() && drop_info_->drop_index == index && + drop_info_->drop_before == drop_before) { + return; + } + + bool is_beneath; + gfx::Rect drop_bounds = GetDropBounds(index, drop_before, &is_beneath); + + if (!drop_info_.get()) { + drop_info_.reset(new DropInfo(index, drop_before, !is_beneath)); + } else { + drop_info_->drop_index = index; + drop_info_->drop_before = drop_before; + if (is_beneath == drop_info_->point_down) { + drop_info_->point_down = !is_beneath; + drop_info_->drop_arrow= GetDropArrowImage(drop_info_->point_down); + } + } + + gtk_window_move(GTK_WINDOW(drop_info_->container), + drop_bounds.x(), drop_bounds.y()); + gtk_window_resize(GTK_WINDOW(drop_info_->container), + drop_bounds.width(), drop_bounds.height()); +} + +void TabStripGtk::CompleteDrop(guchar* data) { + if (!drop_info_.get()) + return; + + const int drop_index = drop_info_->drop_index; + const bool drop_before = drop_info_->drop_before; + + // Hide the drop indicator. + SetDropIndex(-1, false); + + GURL url(reinterpret_cast<char*>(data)); + if (!url.is_valid()) + return; + + if (drop_before) { + // Insert a new tab. + TabContents* contents = + model_->delegate()->CreateTabContentsForURL( + url, GURL(), model_->profile(), PageTransition::TYPED, false, + NULL); + model_->AddTabContents(contents, drop_index, false, + PageTransition::GENERATED, true); + } else { + model_->GetTabContentsAt(drop_index)->controller().LoadURL( + url, GURL(), PageTransition::GENERATED); + model_->SelectTabContentsAt(drop_index, true); + } +} + +// static +GdkPixbuf* TabStripGtk::GetDropArrowImage(bool is_down) { + return ResourceBundle::GetSharedInstance().GetPixbufNamed( + is_down ? IDR_TAB_DROP_DOWN : IDR_TAB_DROP_UP); +} + +// TabStripGtk::DropInfo ------------------------------------------------------- + +TabStripGtk::DropInfo::DropInfo(int drop_index, bool drop_before, + bool point_down) + : drop_index(drop_index), + drop_before(drop_before), + point_down(point_down) { + container = gtk_window_new(GTK_WINDOW_POPUP); + SetContainerColorMap(); + gtk_widget_set_app_paintable(container, TRUE); + g_signal_connect(G_OBJECT(container), "expose-event", + G_CALLBACK(OnExposeEvent), this); + gtk_widget_add_events(container, GDK_STRUCTURE_MASK); + gtk_widget_show_all(container); + + drop_arrow = GetDropArrowImage(point_down); + + gtk_window_move(GTK_WINDOW(container), 0, 0); + gtk_window_resize(GTK_WINDOW(container), + drop_indicator_width, drop_indicator_height); +} + +TabStripGtk::DropInfo::~DropInfo() { + gtk_widget_destroy(container); +} + +// static +gboolean TabStripGtk::DropInfo::OnExposeEvent(GtkWidget* widget, + GdkEventExpose* event, + DropInfo* drop_info) { + if (gtk_util::IsScreenComposited()) { + drop_info->SetContainerTransparency(); + } else { + drop_info->SetContainerShapeMask(); + } + + gdk_pixbuf_render_to_drawable(drop_info->drop_arrow, + drop_info->container->window, + 0, 0, 0, + 0, 0, + drop_indicator_width, + drop_indicator_height, + GDK_RGB_DITHER_NONE, 0, 0); + + return FALSE; +} + +// Sets the color map of the container window to allow the window to be +// transparent. +void TabStripGtk::DropInfo::SetContainerColorMap() { + GdkScreen* screen = gtk_widget_get_screen(container); + GdkColormap* colormap = gdk_screen_get_rgba_colormap(screen); + + // If rgba is not available, use rgb instead. + if (!colormap) + colormap = gdk_screen_get_rgb_colormap(screen); + + gtk_widget_set_colormap(container, colormap); +} + +// Sets full transparency for the container window. This is used if +// compositing is available for the screen. +void TabStripGtk::DropInfo::SetContainerTransparency() { + cairo_t* cairo_context = gdk_cairo_create(container->window); + if (!cairo_context) + return; + + // Make the background of the dragged tab window fully transparent. All of + // the content of the window (child widgets) will be completely opaque. + + cairo_scale(cairo_context, static_cast<double>(drop_indicator_width), + static_cast<double>(drop_indicator_height)); + cairo_set_source_rgba(cairo_context, 1.0f, 1.0f, 1.0f, 0.0f); + cairo_set_operator(cairo_context, CAIRO_OPERATOR_SOURCE); + cairo_paint(cairo_context); + cairo_destroy(cairo_context); +} + +// Sets the shape mask for the container window to emulate a transparent +// container window. This is used if compositing is not available for the +// screen. +void TabStripGtk::DropInfo::SetContainerShapeMask() { + // Create a 1bpp bitmap the size of |container|. + GdkPixmap* pixmap = gdk_pixmap_new(NULL, + drop_indicator_width, + drop_indicator_height, 1); + cairo_t* cairo_context = gdk_cairo_create(GDK_DRAWABLE(pixmap)); + + // Set the transparency. + cairo_set_source_rgba(cairo_context, 1, 1, 1, 0); + + // Blit the rendered bitmap into a pixmap. Any pixel set in the pixmap will + // be opaque in the container window. + cairo_set_operator(cairo_context, CAIRO_OPERATOR_SOURCE); + gdk_cairo_set_source_pixbuf(cairo_context, drop_arrow, 0, 0); + cairo_paint(cairo_context); + cairo_destroy(cairo_context); + + // Set the shape mask. + gdk_window_shape_combine_mask(container->window, pixmap, 0, 0); + g_object_unref(pixmap); +} + // Called from: // - animation tick void TabStripGtk::AnimationLayout(double unselected_width) { @@ -1142,6 +1404,53 @@ gboolean TabStripGtk::OnButtonPress(GtkWidget* widget, GdkEventButton* event, } // static +gboolean TabStripGtk::OnDragMotion(GtkWidget* widget, GdkDragContext* context, + gint x, gint y, guint time, + TabStripGtk* tabstrip) { + tabstrip->UpdateDropIndex(context, x, y); + return TRUE; +} + +// static +gboolean TabStripGtk::OnDragDrop(GtkWidget* widget, GdkDragContext* context, + gint x, gint y, guint time, + TabStripGtk* tabstrip) { + if (!tabstrip->drop_info_.get()) + return FALSE; + + GtkTargetList* list = gtk_target_list_new(kTargetTable, kTargetTableSize); + DCHECK(list); + + GList* target = context->targets; + for (; target != NULL; target = target->next) { + guint info; + GdkAtom target_atom = GDK_POINTER_TO_ATOM(target->data); + if (gtk_target_list_find(list, target_atom, &info)) { + gtk_drag_get_data(widget, context, target_atom, time); + } + } + + g_free(list); + return TRUE; +} + +// static +gboolean TabStripGtk::OnDragDataReceived(GtkWidget* widget, + GdkDragContext* context, + gint x, gint y, + GtkSelectionData* data, + guint info, guint time, + TabStripGtk* tabstrip) { + // TODO(jhawkins): Parse URI lists. + if (info == dnd::X_CHROME_STRING || info == dnd::X_CHROME_TEXT_PLAIN) { + tabstrip->CompleteDrop(data->data); + gtk_drag_finish(context, TRUE, TRUE, time); + } + + return TRUE; +} + +// static void TabStripGtk::OnNewTabClicked(GtkWidget* widget, TabStripGtk* tabstrip) { tabstrip->model_->delegate()->AddBlankTab(true); } diff --git a/chrome/browser/gtk/tabs/tab_strip_gtk.h b/chrome/browser/gtk/tabs/tab_strip_gtk.h index 0b3a809..ddb79b0 100644 --- a/chrome/browser/gtk/tabs/tab_strip_gtk.h +++ b/chrome/browser/gtk/tabs/tab_strip_gtk.h @@ -121,6 +121,55 @@ class TabStripGtk : public TabStripModelObserver, gfx::Rect ideal_bounds; }; + // Used during a drop session of a url. Tracks the position of the drop as + // well as a window used to highlight where the drop occurs. + class DropInfo { + public: + DropInfo(int index, bool drop_before, bool point_down); + ~DropInfo(); + + // TODO(jhawkins): Factor out this code into a TransparentContainer class. + + // expose-event handler that redraws the drop indicator. + static gboolean OnExposeEvent(GtkWidget* widget, GdkEventExpose* event, + DropInfo* drop_info); + + // Sets the color map of the container window to allow the window to be + // transparent. + void SetContainerColorMap(); + + // Sets full transparency for the container window. This is used if + // compositing is available for the screen. + void SetContainerTransparency(); + + // Sets the shape mask for the container window to emulate a transparent + // container window. This is used if compositing is not available for the + // screen. + void SetContainerShapeMask(); + + // Index of the tab to drop on. If drop_before is true, the drop should + // occur between the tab at drop_index - 1 and drop_index. + // WARNING: if drop_before is true it is possible this will == tab_count, + // which indicates the drop should create a new tab at the end of the tabs. + int drop_index; + bool drop_before; + + // Direction the arrow should point in. If true, the arrow is displayed + // above the tab and points down. If false, the arrow is displayed beneath + // the tab and points up. + bool point_down; + + // Transparent container window used to render the drop indicator over the + // tabstrip and toolbar. + GtkWidget* container; + + // The drop indicator image. + GdkPixbuf* drop_arrow; + + private: + DISALLOW_COPY_AND_ASSIGN(DropInfo); + }; + // expose-event handler that redraws the tabstrip static gboolean OnExpose(GtkWidget* widget, GdkEventExpose* e, TabStripGtk* tabstrip); @@ -133,6 +182,23 @@ class TabStripGtk : public TabStripModelObserver, static gboolean OnButtonPress(GtkWidget* widget, GdkEventButton* event, TabStripGtk* tabstrip); + // drag-motion handler that is signaled when the user performs a drag in the + // tabstrip bounds. + static gboolean OnDragMotion(GtkWidget* widget, GdkDragContext* context, + gint x, gint y, guint time, + TabStripGtk* tabstrip); + + // drag-drop handler that is notified when the user finishes a drag. + static gboolean OnDragDrop(GtkWidget* widget, GdkDragContext* context, + gint x, gint y, guint time, + TabStripGtk* tabstrip); + + // drag-data-received handler that receives the data assocated with the drag. + static gboolean OnDragDataReceived(GtkWidget* widget, GdkDragContext* context, + gint x, gint y, GtkSelectionData* data, + guint info, guint time, + TabStripGtk* tabstrip); + // Handles the clicked signal from the new tab button. static void OnNewTabClicked(GtkWidget* widget, TabStripGtk* tabstrip); @@ -200,6 +266,27 @@ class TabStripGtk : public TabStripModelObserver, virtual bool IsItemChecked(int command_id) const; virtual void ExecuteCommand(int command_id); + // -- Link Drag & Drop ------------------------------------------------------ + + // Returns the bounds to render the drop at, in screen coordinates. Sets + // |is_beneath| to indicate whether the arrow is beneath the tab, or above + // it. + gfx::Rect GetDropBounds(int drop_index, bool drop_before, bool* is_beneath); + + // Updates the location of the drop based on the event. + void UpdateDropIndex(GdkDragContext* context, gint x, gint y); + + // Sets the location of the drop, repainting as necessary. + void SetDropIndex(int index, bool drop_before); + + // Determines whether the data is acceptable by the tabstrip and opens a new + // tab with the data as URL if it is. + void CompleteDrop(guchar* data); + + // Returns the image to use for indicating a drop on a tab. If is_down is + // true, this returns an arrow pointing down. + static GdkPixbuf* GetDropArrowImage(bool is_down); + // -- Animations ------------------------------------------------------------- // A generic Layout method for various classes of TabStrip animations, @@ -264,6 +351,9 @@ class TabStripGtk : public TabStripModelObserver, // The New Tab button. scoped_ptr<CustomDrawButton> newtab_button_; + // Valid for the lifetime of a drag over us. + scoped_ptr<DropInfo> drop_info_; + // The controller for a drag initiated from a Tab. Valid for the lifetime of // the drag session. scoped_ptr<DraggedTabControllerGtk> drag_controller_; diff --git a/chrome/common/gtk_util.cc b/chrome/common/gtk_util.cc index 515482b..c3369d9 100644 --- a/chrome/common/gtk_util.cc +++ b/chrome/common/gtk_util.cc @@ -93,6 +93,14 @@ gfx::Rect GetWidgetScreenBounds(GtkWidget* widget) { widget->allocation.width, widget->allocation.height); } +void ConvertWidgetPointToScreen(GtkWidget* widget, gfx::Point* p) { + DCHECK(widget); + DCHECK(p); + + gfx::Point position = GetWidgetScreenPosition(widget); + p->SetPoint(p->x() + position.x(), p->y() + position.y()); +} + void InitRCStyles() { static const char kRCText[] = // Make our dialogs styled like the GNOME HIG. @@ -151,4 +159,9 @@ std::string ConvertAcceleratorsFromWindowsStyle(const std::string& label) { return ret; } +bool IsScreenComposited() { + GdkScreen* screen = gdk_screen_get_default(); + return gdk_screen_is_composited(screen) == TRUE; +} + } // namespace gtk_util diff --git a/chrome/common/gtk_util.h b/chrome/common/gtk_util.h index 5e5a275..4f42380 100644 --- a/chrome/common/gtk_util.h +++ b/chrome/common/gtk_util.h @@ -60,6 +60,9 @@ gfx::Point GetWidgetScreenPosition(GtkWidget* widget); // Returns the bounds of the specified widget in screen coordinates. gfx::Rect GetWidgetScreenBounds(GtkWidget* widget); +// Converts a point in a widget to screen coordinates. +void ConvertWidgetPointToScreen(GtkWidget* widget, gfx::Point* p); + // Initialize some GTK settings so that our dialogs are consistent. void InitRCStyles(); @@ -73,6 +76,9 @@ void CenterWidgetInHBox(GtkWidget* hbox, GtkWidget* widget, bool pack_at_end, // accelerators. Windows uses & with && as an escape for &.) std::string ConvertAcceleratorsFromWindowsStyle(const std::string& label); +// Returns true if the screen is composited, false otherwise. +bool IsScreenComposited(); + } // namespace gtk_util #endif // CHROME_COMMON_GTK_UTIL_H_ |