Home | History | Annotate | Download | only in gtk
      1 /*
      2  *  Copyright (C) 2007, 2009 Holger Hans Peter Freyther zecke (at) selfish.org
      3  *  Copyright (C) 2010 Gustavo Noronha Silva <gns (at) gnome.org>
      4  *
      5  *  This library is free software; you can redistribute it and/or
      6  *  modify it under the terms of the GNU Lesser General Public
      7  *  License as published by the Free Software Foundation; either
      8  *  version 2 of the License, or (at your option) any later version.
      9  *
     10  *  This library is distributed in the hope that it will be useful,
     11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
     12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     13  *  Lesser General Public License for more details.
     14  *
     15  *  You should have received a copy of the GNU Lesser General Public
     16  *  License along with this library; if not, write to the Free Software
     17  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
     18  */
     19 
     20 #include "config.h"
     21 #include "ScrollbarGtk.h"
     22 
     23 #include "IntRect.h"
     24 #include "GraphicsContext.h"
     25 #include "FrameView.h"
     26 #include "ScrollbarTheme.h"
     27 
     28 #include <gtk/gtk.h>
     29 
     30 using namespace WebCore;
     31 
     32 PassRefPtr<Scrollbar> Scrollbar::createNativeScrollbar(ScrollbarClient* client, ScrollbarOrientation orientation, ScrollbarControlSize size)
     33 {
     34     return adoptRef(new ScrollbarGtk(client, orientation, size));
     35 }
     36 
     37 PassRefPtr<ScrollbarGtk> ScrollbarGtk::createScrollbar(ScrollbarClient* client, ScrollbarOrientation orientation, GtkAdjustment* adj)
     38 {
     39     return adoptRef(new ScrollbarGtk(client, orientation, adj));
     40 }
     41 
     42 static gboolean gtkScrollEventCallback(GtkWidget* widget, GdkEventScroll* event, ScrollbarGtk*)
     43 {
     44     /* Scroll only if our parent rejects the scroll event. The rationale for
     45      * this is that we want the main frame to scroll when we move the mouse
     46      * wheel over a child scrollbar in most cases. */
     47     return gtk_widget_event(gtk_widget_get_parent(widget), reinterpret_cast<GdkEvent*>(event));
     48 }
     49 
     50 ScrollbarGtk::ScrollbarGtk(ScrollbarClient* client, ScrollbarOrientation orientation,
     51                            ScrollbarControlSize controlSize)
     52     : Scrollbar(client, orientation, controlSize)
     53     , m_adjustment(GTK_ADJUSTMENT(gtk_adjustment_new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0)))
     54 {
     55     GtkWidget* scrollBar = orientation == HorizontalScrollbar ?
     56                            gtk_hscrollbar_new(m_adjustment):
     57                            gtk_vscrollbar_new(m_adjustment);
     58     gtk_widget_show(scrollBar);
     59     g_object_ref(m_adjustment);
     60     g_signal_connect(m_adjustment, "value-changed", G_CALLBACK(ScrollbarGtk::gtkValueChanged), this);
     61     g_signal_connect(scrollBar, "scroll-event", G_CALLBACK(gtkScrollEventCallback), this);
     62 
     63     setPlatformWidget(scrollBar);
     64 
     65     /*
     66      * assign a sane default width and height to the Scrollbar, otherwise
     67      * we will end up with a 0 width scrollbar.
     68      */
     69     resize(ScrollbarTheme::nativeTheme()->scrollbarThickness(),
     70            ScrollbarTheme::nativeTheme()->scrollbarThickness());
     71 }
     72 
     73 // Create a ScrollbarGtk on top of an existing GtkAdjustment but do not create a
     74 // GtkScrollbar on top of this adjustment. The goal is to have a WebCore::Scrollbar
     75 // that will manipulate the GtkAdjustment properties, will react to the changed
     76 // value but will not consume any space on the screen and will not be painted
     77 // at all. It is achieved by not calling setPlatformWidget.
     78 ScrollbarGtk::ScrollbarGtk(ScrollbarClient* client, ScrollbarOrientation orientation, GtkAdjustment* adjustment)
     79     : Scrollbar(client, orientation, RegularScrollbar)
     80     , m_adjustment(adjustment)
     81 {
     82     g_object_ref(m_adjustment);
     83     g_signal_connect(m_adjustment, "value-changed", G_CALLBACK(ScrollbarGtk::gtkValueChanged), this);
     84 
     85     // We have nothing to show as we are solely operating on the GtkAdjustment
     86     resize(0, 0);
     87 }
     88 
     89 ScrollbarGtk::~ScrollbarGtk()
     90 {
     91     if (m_adjustment)
     92         detachAdjustment();
     93 }
     94 
     95 void ScrollbarGtk::attachAdjustment(GtkAdjustment* adjustment)
     96 {
     97     if (platformWidget())
     98         return;
     99 
    100     if (m_adjustment)
    101         detachAdjustment();
    102 
    103     m_adjustment = adjustment;
    104 
    105     g_object_ref(m_adjustment);
    106     g_signal_connect(m_adjustment, "value-changed", G_CALLBACK(ScrollbarGtk::gtkValueChanged), this);
    107 
    108     updateThumbProportion();
    109     updateThumbPosition();
    110 }
    111 
    112 void ScrollbarGtk::detachAdjustment()
    113 {
    114     if (!m_adjustment)
    115         return;
    116 
    117     g_signal_handlers_disconnect_by_func(G_OBJECT(m_adjustment), (gpointer)ScrollbarGtk::gtkValueChanged, this);
    118 
    119     // For the case where we only operate on the GtkAdjustment it is best to
    120     // reset the values so that the surrounding scrollbar gets updated, or
    121     // e.g. for a GtkScrolledWindow the scrollbar gets hidden.
    122     m_adjustment->lower = 0;
    123     m_adjustment->upper = 0;
    124     m_adjustment->value = 0;
    125     gtk_adjustment_changed(m_adjustment);
    126     gtk_adjustment_value_changed(m_adjustment);
    127     g_object_unref(m_adjustment);
    128     m_adjustment = 0;
    129 }
    130 
    131 IntPoint ScrollbarGtk::getLocationInParentWindow(const IntRect& rect)
    132 {
    133     IntPoint loc;
    134 
    135     if (parent()->isScrollViewScrollbar(this))
    136         loc = parent()->convertToContainingWindow(rect.location());
    137     else
    138         loc = parent()->contentsToWindow(rect.location());
    139 
    140     return loc;
    141 }
    142 
    143 void ScrollbarGtk::frameRectsChanged()
    144 {
    145     if (!parent() || !platformWidget())
    146         return;
    147 
    148     IntPoint loc = getLocationInParentWindow(frameRect());
    149 
    150     // Don't allow the allocation size to be negative
    151     IntSize sz = frameRect().size();
    152     sz.clampNegativeToZero();
    153 
    154     GtkAllocation allocation = { loc.x(), loc.y(), sz.width(), sz.height() };
    155     gtk_widget_size_allocate(platformWidget(), &allocation);
    156 }
    157 
    158 void ScrollbarGtk::updateThumbPosition()
    159 {
    160     if (m_adjustment->value != m_currentPos) {
    161         m_adjustment->value = m_currentPos;
    162         gtk_adjustment_value_changed(m_adjustment);
    163     }
    164 }
    165 
    166 void ScrollbarGtk::updateThumbProportion()
    167 {
    168     m_adjustment->step_increment = m_lineStep;
    169     m_adjustment->page_increment = m_pageStep;
    170     m_adjustment->page_size = m_visibleSize;
    171     m_adjustment->upper = m_totalSize;
    172     gtk_adjustment_changed(m_adjustment);
    173 }
    174 
    175 void ScrollbarGtk::setFrameRect(const IntRect& rect)
    176 {
    177     Widget::setFrameRect(rect);
    178     frameRectsChanged();
    179 }
    180 
    181 void ScrollbarGtk::gtkValueChanged(GtkAdjustment*, ScrollbarGtk* that)
    182 {
    183     that->setValue(static_cast<int>(gtk_adjustment_get_value(that->m_adjustment)));
    184 }
    185 
    186 void ScrollbarGtk::setEnabled(bool shouldEnable)
    187 {
    188     if (enabled() == shouldEnable)
    189         return;
    190 
    191     Scrollbar::setEnabled(shouldEnable);
    192     if (platformWidget())
    193         gtk_widget_set_sensitive(platformWidget(), shouldEnable);
    194 }
    195 
    196 /*
    197  * Strategy to painting a Widget:
    198  *  1.) do not paint if there is no GtkWidget set
    199  *  2.) We assume that GTK_NO_WINDOW is set and that frameRectsChanged positioned
    200  *      the widget correctly. ATM we do not honor the GraphicsContext translation.
    201  */
    202 void ScrollbarGtk::paint(GraphicsContext* context, const IntRect& rect)
    203 {
    204     if (!platformWidget())
    205         return;
    206 
    207     if (!context->gdkExposeEvent())
    208         return;
    209 
    210     GtkWidget* widget = platformWidget();
    211     ASSERT(GTK_WIDGET_NO_WINDOW(widget));
    212 
    213     GdkEvent* event = gdk_event_new(GDK_EXPOSE);
    214     event->expose = *context->gdkExposeEvent();
    215     event->expose.area = static_cast<GdkRectangle>(rect);
    216 
    217     IntPoint loc = getLocationInParentWindow(rect);
    218 
    219     event->expose.area.x = loc.x();
    220     event->expose.area.y = loc.y();
    221 
    222     event->expose.region = gdk_region_rectangle(&event->expose.area);
    223 
    224     /*
    225      * This will be unref'ed by gdk_event_free.
    226      */
    227     g_object_ref(event->expose.window);
    228 
    229     /*
    230      * If we are going to paint do the translation and GtkAllocation manipulation.
    231      */
    232     if (!gdk_region_empty(event->expose.region))
    233         gtk_widget_send_expose(widget, event);
    234 
    235     gdk_event_free(event);
    236 }
    237