Home | History | Annotate | Download | only in panels
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/ui/gtk/panels/panel_drag_gtk.h"
      6 
      7 #include <gdk/gdkkeysyms.h>
      8 
      9 #include "chrome/browser/ui/panels/panel.h"
     10 #include "chrome/browser/ui/panels/panel_constants.h"
     11 #include "chrome/browser/ui/panels/panel_manager.h"
     12 #include "ui/gfx/gtk_util.h"
     13 
     14 namespace {
     15 
     16 panel::ResizingSides GdkWindowEdgeToResizingSide(GdkWindowEdge edge) {
     17   switch (edge) {
     18   case GDK_WINDOW_EDGE_NORTH_WEST:
     19     return panel::RESIZE_TOP_LEFT;
     20   case GDK_WINDOW_EDGE_NORTH:
     21     return panel::RESIZE_TOP;
     22   case GDK_WINDOW_EDGE_NORTH_EAST:
     23     return panel::RESIZE_TOP_RIGHT;
     24   case GDK_WINDOW_EDGE_WEST:
     25     return panel::RESIZE_LEFT;
     26   case GDK_WINDOW_EDGE_EAST:
     27     return panel::RESIZE_RIGHT;
     28   case GDK_WINDOW_EDGE_SOUTH_WEST:
     29     return panel::RESIZE_BOTTOM_LEFT;
     30   case GDK_WINDOW_EDGE_SOUTH:
     31     return panel::RESIZE_BOTTOM;
     32   case GDK_WINDOW_EDGE_SOUTH_EAST:
     33     return panel::RESIZE_BOTTOM_RIGHT;
     34   default:
     35     return panel::RESIZE_NONE;
     36   }
     37 }
     38 
     39 }  // namespace
     40 
     41 // Virtual base class to abstract move vs resize drag logic.
     42 class PanelDragDelegate {
     43  public:
     44   explicit PanelDragDelegate(Panel* panel) : panel_(panel) {}
     45   virtual ~PanelDragDelegate() {}
     46 
     47   Panel* panel() const { return panel_; }
     48   PanelManager* panel_manager() const { return panel_->manager(); }
     49 
     50   // |point| is the mouse location in screen coordinates.
     51   virtual void DragStarted(gfx::Point point) = 0;
     52   virtual void Dragged(gfx::Point point) = 0;
     53 
     54   // |canceled| is true to abort the drag.
     55   virtual void DragEnded(bool canceled) = 0;
     56 
     57  private:
     58   Panel* panel_;  // Weak pointer to the panel being dragged.
     59 
     60   DISALLOW_COPY_AND_ASSIGN(PanelDragDelegate);
     61 };
     62 
     63 // Delegate for moving a panel by dragging the mouse.
     64 class MoveDragDelegate : public PanelDragDelegate {
     65  public:
     66   explicit MoveDragDelegate(Panel* panel)
     67       : PanelDragDelegate(panel) {}
     68   virtual ~MoveDragDelegate() {}
     69 
     70   virtual void DragStarted(gfx::Point point) OVERRIDE {
     71     panel_manager()->StartDragging(panel(), point);
     72   }
     73   virtual void Dragged(gfx::Point point) OVERRIDE {
     74     panel_manager()->Drag(point);
     75   }
     76   virtual void DragEnded(bool canceled) OVERRIDE {
     77     panel_manager()->EndDragging(canceled);
     78   }
     79 
     80   DISALLOW_COPY_AND_ASSIGN(MoveDragDelegate);
     81 };
     82 
     83 // Delegate for resizing a panel by dragging the mouse.
     84 class ResizeDragDelegate : public PanelDragDelegate {
     85  public:
     86   ResizeDragDelegate(Panel* panel, GdkWindowEdge edge)
     87       : PanelDragDelegate(panel),
     88         resizing_side_(GdkWindowEdgeToResizingSide(edge)) {}
     89   virtual ~ResizeDragDelegate() {}
     90 
     91   virtual void DragStarted(gfx::Point point) OVERRIDE {
     92     panel_manager()->StartResizingByMouse(panel(), point, resizing_side_);
     93   }
     94   virtual void Dragged(gfx::Point point) OVERRIDE {
     95     panel_manager()->ResizeByMouse(point);
     96   }
     97   virtual void DragEnded(bool canceled) OVERRIDE {
     98     panel_manager()->EndResizingByMouse(canceled);
     99   }
    100  private:
    101   // The edge from which the panel is being resized.
    102   panel::ResizingSides resizing_side_;
    103 
    104   DISALLOW_COPY_AND_ASSIGN(ResizeDragDelegate);
    105 };
    106 
    107 // Panel drag helper for processing mouse and keyboard events while
    108 // the left mouse button is pressed.
    109 PanelDragGtk::PanelDragGtk(Panel* panel)
    110     : panel_(panel),
    111       drag_state_(NOT_DRAGGING),
    112       initial_mouse_down_(NULL),
    113       click_handler_(NULL),
    114       drag_delegate_(NULL) {
    115   // Create an invisible event box to receive mouse and key events.
    116   drag_widget_ = gtk_event_box_new();
    117   gtk_event_box_set_visible_window(GTK_EVENT_BOX(drag_widget_), FALSE);
    118 
    119   // Connect signals for events during a drag.
    120   g_signal_connect(drag_widget_, "motion-notify-event",
    121                    G_CALLBACK(OnMouseMoveEventThunk), this);
    122   g_signal_connect(drag_widget_, "key-press-event",
    123                    G_CALLBACK(OnKeyPressEventThunk), this);
    124   g_signal_connect(drag_widget_, "key-release-event",
    125                    G_CALLBACK(OnKeyReleaseEventThunk), this);
    126   g_signal_connect(drag_widget_, "button-press-event",
    127                    G_CALLBACK(OnButtonPressEventThunk), this);
    128   g_signal_connect(drag_widget_, "button-release-event",
    129                    G_CALLBACK(OnButtonReleaseEventThunk), this);
    130   g_signal_connect(drag_widget_, "grab-broken-event",
    131                    G_CALLBACK(OnGrabBrokenEventThunk), this);
    132 }
    133 
    134 PanelDragGtk::~PanelDragGtk() {
    135   EndDrag(true);  // Clean up drag state.
    136   ReleasePointerAndKeyboardGrab();
    137 }
    138 
    139 void PanelDragGtk::AssertCleanState() {
    140   DCHECK_EQ(NOT_DRAGGING, drag_state_);
    141   DCHECK(!drag_delegate_);
    142   DCHECK(!initial_mouse_down_);
    143   DCHECK(!click_handler_);
    144 }
    145 
    146 void PanelDragGtk::InitialWindowEdgeMousePress(GdkEventButton* event,
    147                                                GdkCursor* cursor,
    148                                                GdkWindowEdge& edge) {
    149   AssertCleanState();
    150   drag_delegate_ = new ResizeDragDelegate(panel_, edge);
    151   drag_state_ = DRAG_CAN_START;
    152   GrabPointerAndKeyboard(event, cursor);
    153 }
    154 
    155 void PanelDragGtk::InitialTitlebarMousePress(GdkEventButton* event,
    156                                              GtkWidget* titlebar_widget) {
    157   AssertCleanState();
    158   click_handler_ = titlebar_widget;
    159   drag_delegate_ = new MoveDragDelegate(panel_);
    160   drag_state_ = DRAG_CAN_START;
    161   GrabPointerAndKeyboard(event, gfx::GetCursor(GDK_FLEUR));  // Drag cursor.
    162 }
    163 
    164 void PanelDragGtk::GrabPointerAndKeyboard(GdkEventButton* event,
    165                                           GdkCursor* cursor) {
    166   // Remember initial mouse event for use in determining when drag
    167   // threshold has been exceeded.
    168   initial_mouse_down_ = gdk_event_copy(reinterpret_cast<GdkEvent*>(event));
    169 
    170   // Grab pointer and keyboard to make sure we have the focus and get
    171   // all mouse and keyboard events during the drag.
    172   GdkWindow* gdk_window = gtk_widget_get_window(drag_widget_);
    173   DCHECK(gdk_window);
    174   GdkGrabStatus pointer_grab_status =
    175       gdk_pointer_grab(gdk_window,
    176                        TRUE,
    177                        GdkEventMask(GDK_BUTTON_PRESS_MASK |
    178                                     GDK_BUTTON_RELEASE_MASK |
    179                                     GDK_POINTER_MOTION_MASK),
    180                        NULL,
    181                        cursor,
    182                        event->time);
    183   GdkGrabStatus keyboard_grab_status =
    184       gdk_keyboard_grab(gdk_window, TRUE, event->time);
    185   if (pointer_grab_status != GDK_GRAB_SUCCESS ||
    186       keyboard_grab_status != GDK_GRAB_SUCCESS) {
    187     // Grab could fail if someone else already has the pointer/keyboard
    188     // grabbed. Cancel the drag.
    189     DLOG(ERROR) << "Unable to grab pointer or keyboard (pointer_status="
    190                 << pointer_grab_status << ", keyboard_status="
    191                 << keyboard_grab_status << ")";
    192     EndDrag(true);
    193     ReleasePointerAndKeyboardGrab();
    194     return;
    195   }
    196 
    197   gtk_grab_add(drag_widget_);
    198 }
    199 
    200 void PanelDragGtk::ReleasePointerAndKeyboardGrab() {
    201   DCHECK(!drag_delegate_);
    202   if (drag_state_ == NOT_DRAGGING)
    203     return;
    204 
    205   DCHECK_EQ(DRAG_ENDED_WAITING_FOR_MOUSE_RELEASE, drag_state_);
    206   gdk_pointer_ungrab(GDK_CURRENT_TIME);
    207   gdk_keyboard_ungrab(GDK_CURRENT_TIME);
    208   gtk_grab_remove(drag_widget_);
    209   drag_state_ = NOT_DRAGGING;  // Drag is truly over now.
    210 }
    211 
    212 void PanelDragGtk::EndDrag(bool canceled) {
    213   if (drag_state_ == NOT_DRAGGING ||
    214       drag_state_ == DRAG_ENDED_WAITING_FOR_MOUSE_RELEASE) {
    215     DCHECK(!drag_delegate_);
    216     return;
    217   }
    218 
    219   DCHECK(drag_delegate_);
    220 
    221   if (initial_mouse_down_) {
    222     gdk_event_free(initial_mouse_down_);
    223     initial_mouse_down_ = NULL;
    224   }
    225 
    226   if (drag_state_ == DRAG_IN_PROGRESS) {
    227     drag_delegate_->DragEnded(canceled);
    228   }
    229   drag_state_ = DRAG_ENDED_WAITING_FOR_MOUSE_RELEASE;
    230 
    231   delete drag_delegate_;
    232   drag_delegate_ = NULL;
    233 
    234   click_handler_ = NULL;
    235 }
    236 
    237 gboolean PanelDragGtk::OnMouseMoveEvent(GtkWidget* widget,
    238                                         GdkEventMotion* event) {
    239   DCHECK(drag_state_ != NOT_DRAGGING);
    240 
    241   if (drag_state_ == DRAG_ENDED_WAITING_FOR_MOUSE_RELEASE) {
    242     DCHECK(!drag_delegate_);
    243     return TRUE;
    244   }
    245 
    246   DCHECK(drag_delegate_);
    247 
    248   gdouble new_x_double;
    249   gdouble new_y_double;
    250   gdk_event_get_root_coords(reinterpret_cast<GdkEvent*>(event),
    251                             &new_x_double, &new_y_double);
    252   gint new_x = static_cast<gint>(new_x_double);
    253   gint new_y = static_cast<gint>(new_y_double);
    254 
    255   // Begin dragging only after mouse has moved beyond the drag threshold.
    256   if (drag_state_ == DRAG_CAN_START) {
    257     DCHECK(initial_mouse_down_);
    258     gdouble old_x_double;
    259     gdouble old_y_double;
    260     gdk_event_get_root_coords(initial_mouse_down_,
    261                               &old_x_double, &old_y_double);
    262     gint old_x = static_cast<gint>(old_x_double);
    263     gint old_y = static_cast<gint>(old_y_double);
    264 
    265     if (gtk_drag_check_threshold(drag_widget_, old_x, old_y,
    266                                  new_x, new_y)) {
    267       drag_state_ = DRAG_IN_PROGRESS;
    268       drag_delegate_->DragStarted(gfx::Point(old_x, old_y));
    269       gdk_event_free(initial_mouse_down_);
    270       initial_mouse_down_ = NULL;
    271     }
    272   }
    273 
    274   if (drag_state_ == DRAG_IN_PROGRESS)
    275     drag_delegate_->Dragged(gfx::Point(new_x, new_y));
    276 
    277   return TRUE;
    278 }
    279 
    280 gboolean PanelDragGtk::OnButtonPressEvent(GtkWidget* widget,
    281                                           GdkEventButton* event) {
    282   DCHECK(drag_state_ != NOT_DRAGGING);
    283   return TRUE;
    284 }
    285 
    286 gboolean PanelDragGtk::OnButtonReleaseEvent(GtkWidget* widget,
    287                                             GdkEventButton* event) {
    288   DCHECK(drag_state_ != NOT_DRAGGING);
    289 
    290   if (event->button == 1) {
    291     // Treat release as a mouse click if drag was never started.
    292     if (drag_state_ == DRAG_CAN_START && click_handler_) {
    293       gtk_propagate_event(click_handler_,
    294                           reinterpret_cast<GdkEvent*>(event));
    295     }
    296     // Cleanup state regardless.
    297     EndDrag(false);
    298     ReleasePointerAndKeyboardGrab();
    299   }
    300 
    301   return TRUE;
    302 }
    303 
    304 gboolean PanelDragGtk::OnKeyPressEvent(GtkWidget* widget,
    305                                        GdkEventKey* event) {
    306   DCHECK(drag_state_ != NOT_DRAGGING);
    307   return TRUE;
    308 }
    309 
    310 gboolean PanelDragGtk::OnKeyReleaseEvent(GtkWidget* widget,
    311                                          GdkEventKey* event) {
    312   DCHECK(drag_state_ != NOT_DRAGGING);
    313 
    314   if (drag_state_ == DRAG_ENDED_WAITING_FOR_MOUSE_RELEASE) {
    315     DCHECK(!drag_delegate_);
    316     return TRUE;
    317   }
    318 
    319   DCHECK(drag_delegate_);
    320 
    321   switch (event->keyval) {
    322   case GDK_Escape:
    323     EndDrag(true);  // Cancel drag.
    324     break;
    325   case GDK_Return:
    326   case GDK_KP_Enter:
    327   case GDK_ISO_Enter:
    328   case GDK_space:
    329     EndDrag(false);  // Normal end.
    330     break;
    331   }
    332   return TRUE;
    333 }
    334 
    335 gboolean PanelDragGtk::OnGrabBrokenEvent(GtkWidget* widget,
    336                                          GdkEventGrabBroken* event) {
    337   DCHECK(drag_state_ != NOT_DRAGGING);
    338   EndDrag(true);  // Cancel drag.
    339   ReleasePointerAndKeyboardGrab();
    340   return TRUE;
    341 }
    342