1 /* Nautilus - Floating status bar. 2 * 3 * Copyright (C) 2011 Red Hat Inc. 4 * 5 * This library is free software; you can redistribute it and/or 6 * modify it under the terms of the GNU Library 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 * Library General Public License for more details. 14 * 15 * You should have received a copy of the GNU Library General Public 16 * License along with this library; if not, see <http://www.gnu.org/licenses/>. 17 * 18 * Authors: Cosimo Cecchi <cosimoc@redhat.com> 19 * 20 */ 21 22 #include <config.h> 23 24 #include <string.h> 25 26 #include "nautilus-floating-bar.h" 27 28 #define HOVER_HIDE_TIMEOUT_INTERVAL 100 29 30 struct _NautilusFloatingBar 31 { 32 GtkBox parent; 33 34 gchar *primary_label; 35 gchar *details_label; 36 37 GtkWidget *primary_label_widget; 38 GtkWidget *details_label_widget; 39 GtkWidget *spinner; 40 gboolean show_spinner; 41 gboolean is_interactive; 42 guint hover_timeout_id; 43 }; 44 45 enum 46 { 47 PROP_PRIMARY_LABEL = 1, 48 PROP_DETAILS_LABEL, 49 PROP_SHOW_SPINNER, 50 NUM_PROPERTIES 51 }; 52 53 enum 54 { 55 ACTION, 56 NUM_SIGNALS 57 }; 58 59 static GParamSpec *properties[NUM_PROPERTIES] = { NULL, }; 60 static guint signals[NUM_SIGNALS] = { 0, }; 61 62 G_DEFINE_TYPE (NautilusFloatingBar, nautilus_floating_bar, 63 GTK_TYPE_BOX); 64 65 static void 66 action_button_clicked_cb (GtkButton *button, 67 NautilusFloatingBar *self) 68 { 69 gint action_id; 70 71 action_id = GPOINTER_TO_INT 72 (g_object_get_data (G_OBJECT (button), "action-id")); 73 74 g_signal_emit (self, signals[ACTION], 0, action_id); 75 } 76 77 static void 78 nautilus_floating_bar_finalize (GObject *obj) 79 { 80 NautilusFloatingBar *self = NAUTILUS_FLOATING_BAR (obj); 81 82 nautilus_floating_bar_remove_hover_timeout (self); 83 g_free (self->primary_label); 84 g_free (self->details_label); 85 86 G_OBJECT_CLASS (nautilus_floating_bar_parent_class)->finalize (obj); 87 } 88 89 static void 90 nautilus_floating_bar_get_property (GObject *object, 91 guint property_id, 92 GValue *value, 93 GParamSpec *pspec) 94 { 95 NautilusFloatingBar *self = NAUTILUS_FLOATING_BAR (object); 96 97 switch (property_id) 98 { 99 case PROP_PRIMARY_LABEL: 100 { 101 g_value_set_string (value, self->primary_label); 102 } 103 break; 104 105 case PROP_DETAILS_LABEL: 106 { 107 g_value_set_string (value, self->details_label); 108 } 109 break; 110 111 case PROP_SHOW_SPINNER: 112 { 113 g_value_set_boolean (value, self->show_spinner); 114 } 115 break; 116 117 default: 118 { 119 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); 120 } 121 break; 122 } 123 } 124 125 static void 126 nautilus_floating_bar_set_property (GObject *object, 127 guint property_id, 128 const GValue *value, 129 GParamSpec *pspec) 130 { 131 NautilusFloatingBar *self = NAUTILUS_FLOATING_BAR (object); 132 133 switch (property_id) 134 { 135 case PROP_PRIMARY_LABEL: 136 { 137 nautilus_floating_bar_set_primary_label (self, g_value_get_string (value)); 138 } 139 break; 140 141 case PROP_DETAILS_LABEL: 142 { 143 nautilus_floating_bar_set_details_label (self, g_value_get_string (value)); 144 } 145 break; 146 147 case PROP_SHOW_SPINNER: 148 { 149 nautilus_floating_bar_set_show_spinner (self, g_value_get_boolean (value)); 150 } 151 break; 152 153 default: 154 { 155 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); 156 } 157 break; 158 } 159 } 160 161 static void 162 update_labels (NautilusFloatingBar *self) 163 { 164 gboolean primary_visible, details_visible; 165 166 primary_visible = (self->primary_label != NULL) && 167 (strlen (self->primary_label) > 0); 168 details_visible = (self->details_label != NULL) && 169 (strlen (self->details_label) > 0); 170 171 gtk_label_set_text (GTK_LABEL (self->primary_label_widget), 172 self->primary_label); 173 gtk_widget_set_visible (self->primary_label_widget, primary_visible); 174 175 gtk_label_set_text (GTK_LABEL (self->details_label_widget), 176 self->details_label); 177 gtk_widget_set_visible (self->details_label_widget, details_visible); 178 } 179 180 void 181 nautilus_floating_bar_remove_hover_timeout (NautilusFloatingBar *self) 182 { 183 if (self->hover_timeout_id != 0) 184 { 185 g_source_remove (self->hover_timeout_id); 186 self->hover_timeout_id = 0; 187 } 188 } 189 190 typedef struct 191 { 192 GtkWidget *overlay; 193 GtkWidget *floating_bar; 194 GdkDevice *device; 195 gint y_down_limit; 196 gint y_upper_limit; 197 } CheckPointerData; 198 199 static void 200 check_pointer_data_free (gpointer data) 201 { 202 g_slice_free (CheckPointerData, data); 203 } 204 205 static gboolean 206 check_pointer_timeout (gpointer user_data) 207 { 208 CheckPointerData *data = user_data; 209 gint pointer_y = -1; 210 211 gdk_window_get_device_position (gtk_widget_get_window (data->overlay), data->device, 212 NULL, &pointer_y, NULL); 213 214 if (pointer_y == -1 || pointer_y < data->y_down_limit || pointer_y > data->y_upper_limit) 215 { 216 gtk_widget_show (data->floating_bar); 217 NAUTILUS_FLOATING_BAR (data->floating_bar)->hover_timeout_id = 0; 218 219 return G_SOURCE_REMOVE; 220 } 221 else 222 { 223 gtk_widget_hide (data->floating_bar); 224 } 225 226 return G_SOURCE_CONTINUE; 227 } 228 229 static gboolean 230 overlay_enter_notify_cb (GtkWidget *parent, 231 GdkEventCrossing *event, 232 gpointer user_data) 233 { 234 GtkWidget *widget = user_data; 235 CheckPointerData *data; 236 gint y_pos; 237 238 NautilusFloatingBar *self = NAUTILUS_FLOATING_BAR (widget); 239 240 if (self->hover_timeout_id != 0) 241 { 242 g_source_remove (self->hover_timeout_id); 243 } 244 245 if (event->window != gtk_widget_get_window (widget)) 246 { 247 return GDK_EVENT_PROPAGATE; 248 } 249 250 if (NAUTILUS_FLOATING_BAR (widget)->is_interactive) 251 { 252 return GDK_EVENT_PROPAGATE; 253 } 254 255 gdk_window_get_position (gtk_widget_get_window (widget), NULL, &y_pos); 256 257 data = g_slice_new (CheckPointerData); 258 data->overlay = parent; 259 data->floating_bar = widget; 260 data->device = gdk_event_get_device ((GdkEvent *) event); 261 data->y_down_limit = y_pos; 262 data->y_upper_limit = y_pos + gtk_widget_get_allocated_height (widget); 263 264 self->hover_timeout_id = g_timeout_add_full (G_PRIORITY_DEFAULT, HOVER_HIDE_TIMEOUT_INTERVAL, 265 check_pointer_timeout, data, 266 check_pointer_data_free); 267 268 g_source_set_name_by_id (self->hover_timeout_id, "[nautilus-floating-bar] overlay_enter_notify_cb"); 269 270 return GDK_EVENT_STOP; 271 } 272 273 static void 274 nautilus_floating_bar_parent_set (GtkWidget *widget, 275 GtkWidget *old_parent) 276 { 277 GtkWidget *parent; 278 279 parent = gtk_widget_get_parent (widget); 280 281 if (old_parent != NULL) 282 { 283 g_signal_handlers_disconnect_by_func (old_parent, 284 overlay_enter_notify_cb, widget); 285 } 286 287 if (parent != NULL) 288 { 289 g_signal_connect (parent, "enter-notify-event", 290 G_CALLBACK (overlay_enter_notify_cb), widget); 291 } 292 } 293 294 static void 295 get_padding_and_border (GtkWidget *widget, 296 GtkBorder *border) 297 { 298 GtkStyleContext *context; 299 GtkStateFlags state; 300 GtkBorder tmp; 301 302 context = gtk_widget_get_style_context (widget); 303 state = gtk_widget_get_state_flags (widget); 304 305 gtk_style_context_get_padding (context, state, border); 306 gtk_style_context_get_border (context, state, &tmp); 307 border->top += tmp.top; 308 border->right += tmp.right; 309 border->bottom += tmp.bottom; 310 border->left += tmp.left; 311 } 312 313 static void 314 nautilus_floating_bar_get_preferred_width (GtkWidget *widget, 315 gint *minimum_size, 316 gint *natural_size) 317 { 318 GtkBorder border; 319 320 get_padding_and_border (widget, &border); 321 322 GTK_WIDGET_CLASS (nautilus_floating_bar_parent_class)->get_preferred_width (widget, 323 minimum_size, 324 natural_size); 325 326 *minimum_size += border.left + border.right; 327 *natural_size += border.left + border.right; 328 } 329 330 static void 331 nautilus_floating_bar_get_preferred_width_for_height (GtkWidget *widget, 332 gint height, 333 gint *minimum_size, 334 gint *natural_size) 335 { 336 GtkBorder border; 337 338 get_padding_and_border (widget, &border); 339 340 GTK_WIDGET_CLASS (nautilus_floating_bar_parent_class)->get_preferred_width_for_height (widget, 341 height, 342 minimum_size, 343 natural_size); 344 345 *minimum_size += border.left + border.right; 346 *natural_size += border.left + border.right; 347 } 348 349 static void 350 nautilus_floating_bar_get_preferred_height (GtkWidget *widget, 351 gint *minimum_size, 352 gint *natural_size) 353 { 354 GtkBorder border; 355 356 get_padding_and_border (widget, &border); 357 358 GTK_WIDGET_CLASS (nautilus_floating_bar_parent_class)->get_preferred_height (widget, 359 minimum_size, 360 natural_size); 361 362 *minimum_size += border.top + border.bottom; 363 *natural_size += border.top + border.bottom; 364 } 365 366 static void 367 nautilus_floating_bar_get_preferred_height_for_width (GtkWidget *widget, 368 gint width, 369 gint *minimum_size, 370 gint *natural_size) 371 { 372 GtkBorder border; 373 374 get_padding_and_border (widget, &border); 375 376 GTK_WIDGET_CLASS (nautilus_floating_bar_parent_class)->get_preferred_height_for_width (widget, 377 width, 378 minimum_size, 379 natural_size); 380 381 *minimum_size += border.top + border.bottom; 382 *natural_size += border.top + border.bottom; 383 } 384 385 static void 386 nautilus_floating_bar_constructed (GObject *obj) 387 { 388 NautilusFloatingBar *self = NAUTILUS_FLOATING_BAR (obj); 389 GtkWidget *w, *box, *labels_box; 390 391 G_OBJECT_CLASS (nautilus_floating_bar_parent_class)->constructed (obj); 392 393 box = GTK_WIDGET (obj); 394 395 w = gtk_spinner_new (); 396 gtk_box_pack_start (GTK_BOX (box), w, FALSE, FALSE, 0); 397 gtk_widget_set_visible (w, self->show_spinner); 398 gtk_spinner_start (GTK_SPINNER (w)); 399 self->spinner = w; 400 401 gtk_widget_set_size_request (w, 16, 16); 402 gtk_widget_set_margin_start (w, 8); 403 404 labels_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); 405 gtk_box_pack_start (GTK_BOX (box), labels_box, TRUE, TRUE, 0); 406 g_object_set (labels_box, 407 "margin-top", 2, 408 "margin-bottom", 2, 409 "margin-start", 12, 410 "margin-end", 12, 411 NULL); 412 gtk_widget_show (labels_box); 413 414 w = gtk_label_new (NULL); 415 gtk_label_set_ellipsize (GTK_LABEL (w), PANGO_ELLIPSIZE_MIDDLE); 416 gtk_label_set_single_line_mode (GTK_LABEL (w), TRUE); 417 gtk_container_add (GTK_CONTAINER (labels_box), w); 418 self->primary_label_widget = w; 419 gtk_widget_show (w); 420 421 w = gtk_label_new (NULL); 422 gtk_label_set_single_line_mode (GTK_LABEL (w), TRUE); 423 gtk_container_add (GTK_CONTAINER (labels_box), w); 424 self->details_label_widget = w; 425 gtk_widget_show (w); 426 } 427 428 static void 429 nautilus_floating_bar_init (NautilusFloatingBar *self) 430 { 431 GtkStyleContext *context; 432 433 context = gtk_widget_get_style_context (GTK_WIDGET (self)); 434 gtk_style_context_add_class (context, "floating-bar"); 435 } 436 437 static void 438 nautilus_floating_bar_class_init (NautilusFloatingBarClass *klass) 439 { 440 GObjectClass *oclass = G_OBJECT_CLASS (klass); 441 GtkWidgetClass *wclass = GTK_WIDGET_CLASS (klass); 442 443 oclass->constructed = nautilus_floating_bar_constructed; 444 oclass->set_property = nautilus_floating_bar_set_property; 445 oclass->get_property = nautilus_floating_bar_get_property; 446 oclass->finalize = nautilus_floating_bar_finalize; 447 448 wclass->get_preferred_width = nautilus_floating_bar_get_preferred_width; 449 wclass->get_preferred_width_for_height = nautilus_floating_bar_get_preferred_width_for_height; 450 wclass->get_preferred_height = nautilus_floating_bar_get_preferred_height; 451 wclass->get_preferred_height_for_width = nautilus_floating_bar_get_preferred_height_for_width; 452 wclass->parent_set = nautilus_floating_bar_parent_set; 453 454 properties[PROP_PRIMARY_LABEL] = 455 g_param_spec_string ("primary-label", 456 "Bar's primary label", 457 "Primary label displayed by the bar", 458 NULL, 459 G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS); 460 properties[PROP_DETAILS_LABEL] = 461 g_param_spec_string ("details-label", 462 "Bar's details label", 463 "Details label displayed by the bar", 464 NULL, 465 G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS); 466 properties[PROP_SHOW_SPINNER] = 467 g_param_spec_boolean ("show-spinner", 468 "Show spinner", 469 "Whether a spinner should be shown in the floating bar", 470 FALSE, 471 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); 472 473 signals[ACTION] = 474 g_signal_new ("action", 475 G_TYPE_FROM_CLASS (klass), 476 G_SIGNAL_RUN_LAST, 477 0, NULL, NULL, 478 g_cclosure_marshal_VOID__INT, 479 G_TYPE_NONE, 1, 480 G_TYPE_INT); 481 482 g_object_class_install_properties (oclass, NUM_PROPERTIES, properties); 483 } 484 485 void 486 nautilus_floating_bar_set_primary_label (NautilusFloatingBar *self, 487 const gchar *label) 488 { 489 if (g_strcmp0 (self->primary_label, label) != 0) 490 { 491 g_free (self->primary_label); 492 self->primary_label = g_strdup (label); 493 494 g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PRIMARY_LABEL]); 495 496 update_labels (self); 497 } 498 } 499 500 void 501 nautilus_floating_bar_set_details_label (NautilusFloatingBar *self, 502 const gchar *label) 503 { 504 if (g_strcmp0 (self->details_label, label) != 0) 505 { 506 g_free (self->details_label); 507 self->details_label = g_strdup (label); 508 509 g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DETAILS_LABEL]); 510 511 update_labels (self); 512 } 513 } 514 515 void 516 nautilus_floating_bar_set_labels (NautilusFloatingBar *self, 517 const gchar *primary_label, 518 const gchar *details_label) 519 { 520 nautilus_floating_bar_set_primary_label (self, primary_label); 521 nautilus_floating_bar_set_details_label (self, details_label); 522 } 523 524 void 525 nautilus_floating_bar_set_show_spinner (NautilusFloatingBar *self, 526 gboolean show_spinner) 527 { 528 if (self->show_spinner != show_spinner) 529 { 530 self->show_spinner = show_spinner; 531 gtk_widget_set_visible (self->spinner, 532 show_spinner); 533 534 g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SHOW_SPINNER]); 535 } 536 } 537 538 GtkWidget * 539 nautilus_floating_bar_new (const gchar *primary_label, 540 const gchar *details_label, 541 gboolean show_spinner) 542 { 543 return g_object_new (NAUTILUS_TYPE_FLOATING_BAR, 544 "primary-label", primary_label, 545 "details-label", details_label, 546 "show-spinner", show_spinner, 547 "orientation", GTK_ORIENTATION_HORIZONTAL, 548 "spacing", 8, 549 NULL); 550 } 551 552 void 553 nautilus_floating_bar_add_action (NautilusFloatingBar *self, 554 const gchar *icon_name, 555 gint action_id) 556 { 557 GtkWidget *button; 558 GtkStyleContext *context; 559 560 button = gtk_button_new_from_icon_name (icon_name, GTK_ICON_SIZE_MENU); 561 context = gtk_widget_get_style_context (button); 562 gtk_style_context_add_class (context, "circular"); 563 gtk_style_context_add_class (context, "flat"); 564 gtk_widget_set_valign (button, GTK_ALIGN_CENTER); 565 gtk_box_pack_end (GTK_BOX (self), button, FALSE, FALSE, 0); 566 gtk_widget_show (button); 567 568 g_object_set_data (G_OBJECT (button), "action-id", 569 GINT_TO_POINTER (action_id)); 570 571 g_signal_connect (button, "clicked", 572 G_CALLBACK (action_button_clicked_cb), self); 573 574 self->is_interactive = TRUE; 575 } 576 577 void 578 nautilus_floating_bar_cleanup_actions (NautilusFloatingBar *self) 579 { 580 GtkWidget *widget; 581 GList *children, *l; 582 gpointer data; 583 584 children = gtk_container_get_children (GTK_CONTAINER (self)); 585 l = children; 586 587 while (l != NULL) 588 { 589 widget = l->data; 590 data = g_object_get_data (G_OBJECT (widget), "action-id"); 591 l = l->next; 592 593 if (data != NULL) 594 { 595 /* destroy this */ 596 gtk_widget_destroy (widget); 597 } 598 } 599 600 g_list_free (children); 601 602 self->is_interactive = FALSE; 603 } 604