gtkvscrollbutton.c (13610B)
1 /* GTK - The GIMP Toolkit 2 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald 3 * 4 * This library is free software; you can redistribute it and/or 5 * modify it under the terms of the GNU Library General Public 6 * License as published by the Free Software Foundation; either 7 * version 3 of the License, or (at your option) any later version. 8 * 9 * This library is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 * Library General Public License for more details. 13 * 14 * You should have received a copy of the GNU Library General Public 15 * License along with this library. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 /* 19 * Modified by the GTK+ Team and others 1997-1999. See the AUTHORS 20 * file for a list of people on the GTK+ Team. See the ChangeLog 21 * files for a list of changes. These files are distributed with 22 * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 23 */ 24 25 /* 26 * Simple composite widget to provide vertical scrolling, based 27 * on GtkRange widget code. 28 * Modified by the Sylpheed Team and others 2003 29 */ 30 31 #include <glib.h> 32 #include <gtk/gtk.h> 33 #include "utils.h" 34 #include "gtkvscrollbutton.h" 35 #include "gtkutils.h" 36 37 #define SCROLL_TIMER_LENGTH 20 38 #define SCROLL_INITIAL_DELAY 100 /* must hold button this long before ... */ 39 #define SCROLL_LATER_DELAY 20 /* ... it starts repeating at this rate */ 40 #define SCROLL_DELAY_LENGTH 300 41 42 43 enum { 44 ARG_0, 45 ARG_ADJUSTMENT 46 }; 47 48 static void gtk_vscrollbutton_class_init(GtkVScrollbuttonClass * klass); 49 static void gtk_vscrollbutton_init(GtkVScrollbutton * vscrollbutton); 50 51 GType gtk_vscrollbutton_get_type (void); 52 GtkWidget *gtk_vscrollbutton_new (GtkAdjustment *adjustment); 53 54 static gint gtk_vscrollbutton_button_release (GtkWidget *widget, 55 GdkEventButton *event, 56 GtkVScrollbutton *scrollbutton); 57 58 static void gtk_vscrollbutton_set_adjustment (GtkVScrollbutton *scrollbutton, 59 GtkAdjustment *adjustment); 60 61 static gint gtk_vscrollbutton_button_press (GtkWidget *widget, 62 GdkEventButton *event, 63 GtkVScrollbutton *scrollbutton); 64 65 static gint gtk_vscrollbutton_button_release (GtkWidget *widget, 66 GdkEventButton *event, 67 GtkVScrollbutton *scrollbutton); 68 69 gint gtk_vscrollbutton_scroll (GtkVScrollbutton *scrollbutton); 70 71 static gboolean gtk_vscrollbutton_timer_1st_time(GtkVScrollbutton *scrollbutton); 72 73 static void gtk_vscrollbutton_add_timer (GtkVScrollbutton *scrollbutton); 74 75 static void gtk_vscrollbutton_remove_timer (GtkVScrollbutton *scrollbutton); 76 77 static gboolean gtk_real_vscrollbutton_timer (GtkVScrollbutton *scrollbutton); 78 79 static void gtk_vscrollbutton_set_sensitivity (GtkAdjustment *adjustment, 80 GtkVScrollbutton *scrollbutton); 81 82 GType gtk_vscrollbutton_get_type(void) 83 { 84 static GType vscrollbutton_type = 0; 85 86 if (!vscrollbutton_type) { 87 static const GTypeInfo vscrollbutton_info = { 88 sizeof (GtkVScrollbuttonClass), 89 90 (GBaseInitFunc) NULL, 91 (GBaseFinalizeFunc) NULL, 92 93 (GClassInitFunc) gtk_vscrollbutton_class_init, 94 (GClassFinalizeFunc) NULL, 95 NULL, /* class_data */ 96 97 sizeof (GtkVScrollbutton), 98 0, /* n_preallocs */ 99 (GInstanceInitFunc) gtk_vscrollbutton_init, 100 101 (const GTypeValueTable *) NULL /* value table */ 102 }; 103 104 vscrollbutton_type = g_type_register_static (GTK_TYPE_BOX, "GtkVScrollbutton", &vscrollbutton_info, (GTypeFlags)0); 105 } 106 107 return vscrollbutton_type; 108 } 109 110 static void gtk_vscrollbutton_class_init(GtkVScrollbuttonClass *class) 111 { 112 } 113 114 static GdkCursor *hand_cursor = NULL; 115 116 static gboolean vscroll_visi_notify(GtkWidget *widget, 117 GdkEventVisibility *event, 118 gpointer data) 119 { 120 gdk_window_set_cursor(gtk_widget_get_window(widget), hand_cursor); 121 return FALSE; 122 } 123 124 static gboolean vscroll_leave_notify(GtkWidget *widget, 125 GdkEventCrossing *event, 126 gpointer data) 127 { 128 gdk_window_set_cursor(gtk_widget_get_window(widget), NULL); 129 return FALSE; 130 } 131 132 static gboolean vscroll_enter_notify(GtkWidget *widget, 133 GdkEventCrossing *event, 134 gpointer data) 135 { 136 gdk_window_set_cursor(gtk_widget_get_window(widget), hand_cursor); 137 return FALSE; 138 } 139 140 141 static void gtk_vscrollbutton_init(GtkVScrollbutton *scrollbutton) 142 { 143 GtkWidget *arrow; 144 145 if (!hand_cursor) 146 hand_cursor = gdk_cursor_new_for_display( 147 gtk_widget_get_display(GTK_WIDGET(scrollbutton)),GDK_HAND2); 148 149 scrollbutton->upbutton = gtk_event_box_new(); 150 scrollbutton->downbutton = gtk_event_box_new(); 151 arrow = gtk_image_new_from_icon_name("pan-up-symbolic", GTK_ICON_SIZE_MENU); 152 gtk_widget_show(arrow); 153 gtk_container_add(GTK_CONTAINER(scrollbutton->upbutton), arrow); 154 gtk_widget_set_size_request(scrollbutton->upbutton, -1, 16); 155 arrow = gtk_image_new_from_icon_name("pan-down-symbolic", GTK_ICON_SIZE_MENU); 156 gtk_widget_show(arrow); 157 gtk_container_add(GTK_CONTAINER(scrollbutton->downbutton), arrow); 158 gtk_widget_set_size_request(scrollbutton->downbutton, -1, 16); 159 gtk_widget_set_can_focus(scrollbutton->upbutton, FALSE); 160 gtk_widget_set_can_focus(scrollbutton->downbutton, FALSE); 161 gtk_widget_show(scrollbutton->downbutton); 162 gtk_widget_show(scrollbutton->upbutton); 163 164 g_signal_connect(G_OBJECT(scrollbutton->upbutton), "motion-notify-event", 165 G_CALLBACK(vscroll_visi_notify), NULL); 166 g_signal_connect(G_OBJECT(scrollbutton->upbutton), "leave-notify-event", 167 G_CALLBACK(vscroll_leave_notify), NULL); 168 g_signal_connect(G_OBJECT(scrollbutton->upbutton), "enter-notify-event", 169 G_CALLBACK(vscroll_enter_notify), NULL); 170 g_signal_connect(G_OBJECT(scrollbutton->downbutton), "motion-notify-event", 171 G_CALLBACK(vscroll_visi_notify), NULL); 172 g_signal_connect(G_OBJECT(scrollbutton->downbutton), "leave-notify-event", 173 G_CALLBACK(vscroll_leave_notify), NULL); 174 g_signal_connect(G_OBJECT(scrollbutton->downbutton), "enter-notify-event", 175 G_CALLBACK(vscroll_enter_notify), NULL); 176 177 g_signal_connect(G_OBJECT(scrollbutton->upbutton), 178 "button_press_event", 179 G_CALLBACK(gtk_vscrollbutton_button_press), 180 scrollbutton); 181 g_signal_connect(G_OBJECT(scrollbutton->downbutton), 182 "button_press_event", 183 G_CALLBACK(gtk_vscrollbutton_button_press), 184 scrollbutton); 185 g_signal_connect(G_OBJECT(scrollbutton->upbutton), 186 "button_release_event", 187 G_CALLBACK 188 (gtk_vscrollbutton_button_release), scrollbutton); 189 g_signal_connect(G_OBJECT(scrollbutton->downbutton), 190 "button_release_event", 191 G_CALLBACK 192 (gtk_vscrollbutton_button_release), scrollbutton); 193 gtk_box_pack_start(GTK_BOX(&scrollbutton->vbox), 194 scrollbutton->upbutton, TRUE, TRUE, 0); 195 gtk_box_pack_end(GTK_BOX(&scrollbutton->vbox), 196 scrollbutton->downbutton, TRUE, TRUE, 0); 197 scrollbutton->timer = 0; 198 } 199 200 GtkWidget *gtk_vscrollbutton_new(GtkAdjustment *adjustment) 201 { 202 GtkWidget *vscrollbutton; 203 vscrollbutton = g_object_new (gtk_vscrollbutton_get_type(), 204 NULL); 205 gtk_vscrollbutton_set_adjustment(GTK_VSCROLLBUTTON(vscrollbutton), 206 adjustment); 207 g_signal_connect(G_OBJECT(GTK_VSCROLLBUTTON(vscrollbutton)->adjustment), 208 "value_changed", 209 G_CALLBACK 210 (gtk_vscrollbutton_set_sensitivity), vscrollbutton); 211 g_signal_connect(G_OBJECT(GTK_VSCROLLBUTTON(vscrollbutton)->adjustment), 212 "changed", 213 G_CALLBACK 214 (gtk_vscrollbutton_set_sensitivity), vscrollbutton); 215 return vscrollbutton; 216 } 217 218 219 void gtk_vscrollbutton_set_adjustment(GtkVScrollbutton *scrollbutton, 220 GtkAdjustment *adjustment) 221 { 222 cm_return_if_fail(scrollbutton != NULL); 223 cm_return_if_fail(GTK_IS_VSCROLLBUTTON(scrollbutton)); 224 225 if (!adjustment) 226 adjustment = 227 GTK_ADJUSTMENT(gtk_adjustment_new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0)); 228 else 229 cm_return_if_fail(GTK_IS_ADJUSTMENT(adjustment)); 230 231 if (scrollbutton->adjustment != adjustment) { 232 if (scrollbutton->adjustment) { 233 g_signal_handlers_disconnect_matched(scrollbutton->adjustment, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, (gpointer) scrollbutton); 234 g_object_unref(G_OBJECT(scrollbutton->adjustment)); 235 } 236 237 scrollbutton->adjustment = adjustment; 238 g_object_ref(G_OBJECT(adjustment)); 239 g_object_ref_sink (G_OBJECT(adjustment)); 240 } 241 } 242 243 static gint gtk_vscrollbutton_button_press(GtkWidget *widget, 244 GdkEventButton *event, 245 GtkVScrollbutton *scrollbutton) 246 { 247 if (!gtk_widget_has_focus(widget)) 248 gtk_widget_grab_focus(widget); 249 250 if (scrollbutton->button == 0) { 251 gtk_grab_add(widget); 252 scrollbutton->button = event->button; 253 254 if (widget == scrollbutton->downbutton) 255 scrollbutton->scroll_type = GTK_SCROLL_STEP_FORWARD; 256 else 257 scrollbutton->scroll_type = GTK_SCROLL_STEP_BACKWARD; 258 gtk_vscrollbutton_scroll(scrollbutton); 259 gtk_vscrollbutton_add_timer(scrollbutton); 260 } 261 return TRUE; 262 } 263 264 265 static gint gtk_vscrollbutton_button_release(GtkWidget *widget, 266 GdkEventButton *event, 267 GtkVScrollbutton *scrollbutton) 268 { 269 if (!gtk_widget_has_focus(widget)) 270 gtk_widget_grab_focus(widget); 271 272 if (scrollbutton->button == event->button) { 273 gtk_grab_remove(widget); 274 275 scrollbutton->button = 0; 276 gtk_vscrollbutton_remove_timer(scrollbutton); 277 gtk_vscrollbutton_set_sensitivity(scrollbutton->adjustment, scrollbutton); 278 } 279 return TRUE; 280 } 281 282 gboolean gtk_vscrollbutton_scroll(GtkVScrollbutton *scrollbutton) 283 { 284 gfloat bound; 285 gfloat new_value; 286 gfloat page_size; 287 gfloat value; 288 gboolean return_val; 289 290 cm_return_val_if_fail(scrollbutton != NULL, FALSE); 291 cm_return_val_if_fail(GTK_IS_VSCROLLBUTTON(scrollbutton), FALSE); 292 293 new_value = value = gtk_adjustment_get_value(scrollbutton->adjustment); 294 return_val = TRUE; 295 296 switch (scrollbutton->scroll_type) { 297 298 case GTK_SCROLL_STEP_BACKWARD: 299 new_value = value - gtk_adjustment_get_step_increment(scrollbutton->adjustment); 300 bound = gtk_adjustment_get_lower(scrollbutton->adjustment); 301 if (new_value <= bound) { 302 new_value = bound; 303 return_val = FALSE; 304 scrollbutton->timer = 0; 305 } 306 break; 307 308 case GTK_SCROLL_STEP_FORWARD: 309 new_value = value + gtk_adjustment_get_step_increment(scrollbutton->adjustment); 310 bound = gtk_adjustment_get_upper(scrollbutton->adjustment); 311 page_size = gtk_adjustment_get_page_size(scrollbutton->adjustment); 312 if (new_value >= (bound - page_size)) { 313 new_value = bound - page_size; 314 return_val = FALSE; 315 scrollbutton->timer = 0; 316 } 317 break; 318 319 default: 320 break; 321 322 } 323 324 if (new_value != value) { 325 gtk_adjustment_set_value(scrollbutton->adjustment, new_value); 326 g_signal_emit_by_name(G_OBJECT 327 (scrollbutton->adjustment), 328 "value_changed"); 329 gtk_widget_queue_resize(GTK_WIDGET(scrollbutton)); /* ensure resize */ 330 } 331 332 return return_val; 333 } 334 335 static gboolean 336 gtk_vscrollbutton_timer_1st_time(GtkVScrollbutton *scrollbutton) 337 { 338 /* 339 * If the real timeout function succeeds and the timeout is still set, 340 * replace it with a quicker one so successive scrolling goes faster. 341 */ 342 g_object_ref(G_OBJECT(scrollbutton)); 343 if (scrollbutton->timer) { 344 /* We explicitely remove ourselves here in the paranoia 345 * that due to things happening above in the callback 346 * above, we might have been removed, and another added. 347 */ 348 g_source_remove(scrollbutton->timer); 349 scrollbutton->timer = g_timeout_add(SCROLL_LATER_DELAY, 350 (GSourceFunc) 351 gtk_real_vscrollbutton_timer, 352 scrollbutton); 353 } 354 g_object_unref(G_OBJECT(scrollbutton)); 355 return FALSE; /* don't keep calling this function */ 356 } 357 358 359 static void gtk_vscrollbutton_add_timer(GtkVScrollbutton *scrollbutton) 360 { 361 cm_return_if_fail(scrollbutton != NULL); 362 cm_return_if_fail(GTK_IS_VSCROLLBUTTON(scrollbutton)); 363 364 if (!scrollbutton->timer) { 365 scrollbutton->need_timer = TRUE; 366 scrollbutton->timer = g_timeout_add(SCROLL_INITIAL_DELAY, 367 (GSourceFunc) 368 gtk_vscrollbutton_timer_1st_time, 369 scrollbutton); 370 } 371 } 372 373 static void gtk_vscrollbutton_remove_timer(GtkVScrollbutton *scrollbutton) 374 { 375 cm_return_if_fail(scrollbutton != NULL); 376 cm_return_if_fail(GTK_IS_VSCROLLBUTTON(scrollbutton)); 377 378 if (scrollbutton->timer) { 379 g_source_remove(scrollbutton->timer); 380 scrollbutton->timer = 0; 381 } 382 scrollbutton->need_timer = FALSE; 383 } 384 385 static gboolean gtk_real_vscrollbutton_timer(GtkVScrollbutton *scrollbutton) 386 { 387 gboolean return_val; 388 389 GDK_THREADS_ENTER(); 390 391 return_val = TRUE; 392 if (!scrollbutton->timer) { 393 return_val = FALSE; 394 if (scrollbutton->need_timer) 395 scrollbutton->timer = 396 g_timeout_add(SCROLL_TIMER_LENGTH, 397 (GSourceFunc) gtk_real_vscrollbutton_timer, 398 (gpointer) scrollbutton); 399 else { 400 GDK_THREADS_LEAVE(); 401 return FALSE; 402 } 403 scrollbutton->need_timer = FALSE; 404 } 405 GDK_THREADS_LEAVE(); 406 return_val = gtk_vscrollbutton_scroll(scrollbutton); 407 return return_val; 408 } 409 410 static void gtk_vscrollbutton_set_sensitivity (GtkAdjustment *adjustment, 411 GtkVScrollbutton *scrollbutton) 412 { 413 gfloat value; 414 if (!gtk_widget_get_realized(GTK_WIDGET(scrollbutton))) return; 415 if (scrollbutton->button != 0) return; /* not while something is pressed */ 416 417 value = gtk_adjustment_get_value(adjustment); 418 gtk_widget_set_sensitive(scrollbutton->upbutton, 419 (value > gtk_adjustment_get_lower(adjustment))); 420 gtk_widget_set_sensitive(scrollbutton->downbutton, 421 (value < (gtk_adjustment_get_upper(adjustment) - 422 gtk_adjustment_get_page_size(adjustment)))); 423 }