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