nautilus-floating-bar.c 18.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
/* Nautilus - Floating status bar.
 *
 * Copyright (C) 2011 Red Hat Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * 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 19 20 21 22 23
 *
 * Authors: Cosimo Cecchi <cosimoc@redhat.com>
 *
 */

#include <config.h>

24 25
#include <string.h>

26 27
#include "nautilus-floating-bar.h"

28 29
#define HOVER_HIDE_TIMEOUT_INTERVAL 100

30
struct _NautilusFloatingBar
31
{
32 33
    GtkBox parent;

34 35 36 37 38 39 40 41 42
    gchar *primary_label;
    gchar *details_label;

    GtkWidget *primary_label_widget;
    GtkWidget *details_label_widget;
    GtkWidget *spinner;
    gboolean show_spinner;
    gboolean is_interactive;
    guint hover_timeout_id;
43 44
};

45 46 47 48 49 50
enum
{
    PROP_PRIMARY_LABEL = 1,
    PROP_DETAILS_LABEL,
    PROP_SHOW_SPINNER,
    NUM_PROPERTIES
51 52
};

53 54 55 56
enum
{
    ACTION,
    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 67
action_button_clicked_cb (GtkButton           *button,
                          NautilusFloatingBar *self)
68
{
69
    gint action_id;
70

71 72 73 74
    action_id = GPOINTER_TO_INT
                    (g_object_get_data (G_OBJECT (button), "action-id"));

    g_signal_emit (self, signals[ACTION], 0, action_id);
75 76 77 78 79
}

static void
nautilus_floating_bar_finalize (GObject *obj)
{
80
    NautilusFloatingBar *self = NAUTILUS_FLOATING_BAR (obj);
81

82
    nautilus_floating_bar_remove_hover_timeout (self);
83 84
    g_free (self->primary_label);
    g_free (self->details_label);
85

86
    G_OBJECT_CLASS (nautilus_floating_bar_parent_class)->finalize (obj);
87 88
}

89
static void
90 91 92 93
nautilus_floating_bar_get_property (GObject    *object,
                                    guint       property_id,
                                    GValue     *value,
                                    GParamSpec *pspec)
94
{
95 96 97 98 99 100
    NautilusFloatingBar *self = NAUTILUS_FLOATING_BAR (object);

    switch (property_id)
    {
        case PROP_PRIMARY_LABEL:
        {
101
            g_value_set_string (value, self->primary_label);
102 103 104 105 106
        }
        break;

        case PROP_DETAILS_LABEL:
        {
107
            g_value_set_string (value, self->details_label);
108 109 110 111 112
        }
        break;

        case PROP_SHOW_SPINNER:
        {
113
            g_value_set_boolean (value, self->show_spinner);
114 115 116 117 118 119 120 121 122
        }
        break;

        default:
        {
            G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
        }
        break;
    }
123 124
}

125
static void
126 127 128 129
nautilus_floating_bar_set_property (GObject      *object,
                                    guint         property_id,
                                    const GValue *value,
                                    GParamSpec   *pspec)
130
{
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
    NautilusFloatingBar *self = NAUTILUS_FLOATING_BAR (object);

    switch (property_id)
    {
        case PROP_PRIMARY_LABEL:
        {
            nautilus_floating_bar_set_primary_label (self, g_value_get_string (value));
        }
        break;

        case PROP_DETAILS_LABEL:
        {
            nautilus_floating_bar_set_details_label (self, g_value_get_string (value));
        }
        break;

        case PROP_SHOW_SPINNER:
        {
            nautilus_floating_bar_set_show_spinner (self, g_value_get_boolean (value));
        }
        break;

        default:
        {
            G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
        }
        break;
    }
159 160 161
}

static void
162
update_labels (NautilusFloatingBar *self)
163
{
164
    gboolean primary_visible, details_visible;
165

166 167 168 169
    primary_visible = (self->primary_label != NULL) &&
                      (strlen (self->primary_label) > 0);
    details_visible = (self->details_label != NULL) &&
                      (strlen (self->details_label) > 0);
170

171 172 173
    gtk_label_set_text (GTK_LABEL (self->primary_label_widget),
                        self->primary_label);
    gtk_widget_set_visible (self->primary_label_widget, primary_visible);
174

175 176 177
    gtk_label_set_text (GTK_LABEL (self->details_label_widget),
                        self->details_label);
    gtk_widget_set_visible (self->details_label_widget, details_visible);
178 179
}

180 181 182
void
nautilus_floating_bar_remove_hover_timeout (NautilusFloatingBar *self)
{
183
    if (self->hover_timeout_id != 0)
184
    {
185 186
        g_source_remove (self->hover_timeout_id);
        self->hover_timeout_id = 0;
187
    }
188 189
}

190 191 192 193 194 195 196
typedef struct
{
    GtkWidget *overlay;
    GtkWidget *floating_bar;
    GdkDevice *device;
    gint y_down_limit;
    gint y_upper_limit;
197 198 199 200 201
} CheckPointerData;

static void
check_pointer_data_free (gpointer data)
{
202
    g_slice_free (CheckPointerData, data);
203 204 205 206 207
}

static gboolean
check_pointer_timeout (gpointer user_data)
{
208 209
    CheckPointerData *data = user_data;
    gint pointer_y = -1;
210

211 212
    gdk_window_get_device_position (gtk_widget_get_window (data->overlay), data->device,
                                    NULL, &pointer_y, NULL);
213

214 215 216
    if (pointer_y == -1 || pointer_y < data->y_down_limit || pointer_y > data->y_upper_limit)
    {
        gtk_widget_show (data->floating_bar);
217
        NAUTILUS_FLOATING_BAR (data->floating_bar)->hover_timeout_id = 0;
218

219 220 221 222 223 224
        return G_SOURCE_REMOVE;
    }
    else
    {
        gtk_widget_hide (data->floating_bar);
    }
225

226
    return G_SOURCE_CONTINUE;
227 228
}

229
static gboolean
230 231 232
overlay_event_cb (GtkWidget *parent,
                  GdkEvent  *event,
                  gpointer   user_data)
233
{
234
    NautilusFloatingBar *self = NAUTILUS_FLOATING_BAR (user_data);
235 236 237
    GtkWidget *widget = user_data;
    CheckPointerData *data;
    gint y_pos;
238

239 240 241 242
    if (gdk_event_get_event_type (event) != GDK_ENTER_NOTIFY)
    {
        return GDK_EVENT_PROPAGATE;
    }
243

244
    if (self->hover_timeout_id != 0)
245
    {
246
        g_source_remove (self->hover_timeout_id);
247
    }
248

249
    if (gdk_event_get_window (event) != gtk_widget_get_window (widget))
250 251 252
    {
        return GDK_EVENT_PROPAGATE;
    }
253

254
    if (self->is_interactive)
255 256 257
    {
        return GDK_EVENT_PROPAGATE;
    }
258

259
    gdk_window_get_position (gtk_widget_get_window (widget), NULL, &y_pos);
260

261 262 263
    data = g_slice_new (CheckPointerData);
    data->overlay = parent;
    data->floating_bar = widget;
264
    data->device = gdk_event_get_device (event);
265
    data->y_down_limit = y_pos;
266
    data->y_upper_limit = y_pos + gtk_widget_get_allocated_height (widget);
267

268 269 270
    self->hover_timeout_id = g_timeout_add_full (G_PRIORITY_DEFAULT, HOVER_HIDE_TIMEOUT_INTERVAL,
                                                 check_pointer_timeout, data,
                                                 check_pointer_data_free);
271

272
    g_source_set_name_by_id (self->hover_timeout_id, "[nautilus-floating-bar] overlay_event_cb");
273

274
    return GDK_EVENT_STOP;
275 276 277 278
}

static void
nautilus_floating_bar_parent_set (GtkWidget *widget,
279
                                  GtkWidget *old_parent)
280
{
281
    GtkWidget *parent;
282

283
    parent = gtk_widget_get_parent (widget);
284

285 286 287
    if (old_parent != NULL)
    {
        g_signal_handlers_disconnect_by_func (old_parent,
288
                                              overlay_event_cb, widget);
289
    }
290

291 292
    if (parent != NULL)
    {
293 294
        g_signal_connect (parent, "event",
                          G_CALLBACK (overlay_event_cb), widget);
295
    }
296 297
}

298 299 300 301
static void
get_padding_and_border (GtkWidget *widget,
                        GtkBorder *border)
{
302 303 304 305 306 307 308 309 310 311 312 313 314
    GtkStyleContext *context;
    GtkStateFlags state;
    GtkBorder tmp;

    context = gtk_widget_get_style_context (widget);
    state = gtk_widget_get_state_flags (widget);

    gtk_style_context_get_padding (context, state, border);
    gtk_style_context_get_border (context, state, &tmp);
    border->top += tmp.top;
    border->right += tmp.right;
    border->bottom += tmp.bottom;
    border->left += tmp.left;
315 316 317 318
}

static void
nautilus_floating_bar_get_preferred_width (GtkWidget *widget,
319 320
                                           gint      *minimum_size,
                                           gint      *natural_size)
321
{
322
    GtkBorder border;
323

324
    get_padding_and_border (widget, &border);
325

326 327 328
    GTK_WIDGET_CLASS (nautilus_floating_bar_parent_class)->get_preferred_width (widget,
                                                                                minimum_size,
                                                                                natural_size);
329

330 331
    *minimum_size += border.left + border.right;
    *natural_size += border.left + border.right;
332 333 334 335
}

static void
nautilus_floating_bar_get_preferred_width_for_height (GtkWidget *widget,
336 337 338
                                                      gint       height,
                                                      gint      *minimum_size,
                                                      gint      *natural_size)
339
{
340
    GtkBorder border;
341

342
    get_padding_and_border (widget, &border);
343

344 345 346 347
    GTK_WIDGET_CLASS (nautilus_floating_bar_parent_class)->get_preferred_width_for_height (widget,
                                                                                           height,
                                                                                           minimum_size,
                                                                                           natural_size);
348

349 350
    *minimum_size += border.left + border.right;
    *natural_size += border.left + border.right;
351 352 353 354
}

static void
nautilus_floating_bar_get_preferred_height (GtkWidget *widget,
355 356
                                            gint      *minimum_size,
                                            gint      *natural_size)
357
{
358
    GtkBorder border;
359

360
    get_padding_and_border (widget, &border);
361

362 363 364
    GTK_WIDGET_CLASS (nautilus_floating_bar_parent_class)->get_preferred_height (widget,
                                                                                 minimum_size,
                                                                                 natural_size);
365

366 367
    *minimum_size += border.top + border.bottom;
    *natural_size += border.top + border.bottom;
368 369 370 371
}

static void
nautilus_floating_bar_get_preferred_height_for_width (GtkWidget *widget,
372 373 374
                                                      gint       width,
                                                      gint      *minimum_size,
                                                      gint      *natural_size)
375
{
376
    GtkBorder border;
377

378
    get_padding_and_border (widget, &border);
379

380 381 382 383
    GTK_WIDGET_CLASS (nautilus_floating_bar_parent_class)->get_preferred_height_for_width (widget,
                                                                                           width,
                                                                                           minimum_size,
                                                                                           natural_size);
384

385 386
    *minimum_size += border.top + border.bottom;
    *natural_size += border.top + border.bottom;
387 388
}

389 390 391
static void
nautilus_floating_bar_constructed (GObject *obj)
{
392 393 394 395 396 397 398 399 400
    NautilusFloatingBar *self = NAUTILUS_FLOATING_BAR (obj);
    GtkWidget *w, *box, *labels_box;

    G_OBJECT_CLASS (nautilus_floating_bar_parent_class)->constructed (obj);

    box = GTK_WIDGET (obj);

    w = gtk_spinner_new ();
    gtk_box_pack_start (GTK_BOX (box), w, FALSE, FALSE, 0);
401
    gtk_widget_set_visible (w, self->show_spinner);
402
    gtk_spinner_start (GTK_SPINNER (w));
403
    self->spinner = w;
404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421

    gtk_widget_set_size_request (w, 16, 16);
    gtk_widget_set_margin_start (w, 8);

    labels_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
    gtk_box_pack_start (GTK_BOX (box), labels_box, TRUE, TRUE, 0);
    g_object_set (labels_box,
                  "margin-top", 2,
                  "margin-bottom", 2,
                  "margin-start", 12,
                  "margin-end", 12,
                  NULL);
    gtk_widget_show (labels_box);

    w = gtk_label_new (NULL);
    gtk_label_set_ellipsize (GTK_LABEL (w), PANGO_ELLIPSIZE_MIDDLE);
    gtk_label_set_single_line_mode (GTK_LABEL (w), TRUE);
    gtk_container_add (GTK_CONTAINER (labels_box), w);
422
    self->primary_label_widget = w;
423 424 425 426 427
    gtk_widget_show (w);

    w = gtk_label_new (NULL);
    gtk_label_set_single_line_mode (GTK_LABEL (w), TRUE);
    gtk_container_add (GTK_CONTAINER (labels_box), w);
428
    self->details_label_widget = w;
429
    gtk_widget_show (w);
430 431 432 433 434
}

static void
nautilus_floating_bar_init (NautilusFloatingBar *self)
{
435
    GtkStyleContext *context;
436

437 438
    context = gtk_widget_get_style_context (GTK_WIDGET (self));
    gtk_style_context_add_class (context, "floating-bar");
439 440 441 442 443
}

static void
nautilus_floating_bar_class_init (NautilusFloatingBarClass *klass)
{
444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486
    GObjectClass *oclass = G_OBJECT_CLASS (klass);
    GtkWidgetClass *wclass = GTK_WIDGET_CLASS (klass);

    oclass->constructed = nautilus_floating_bar_constructed;
    oclass->set_property = nautilus_floating_bar_set_property;
    oclass->get_property = nautilus_floating_bar_get_property;
    oclass->finalize = nautilus_floating_bar_finalize;

    wclass->get_preferred_width = nautilus_floating_bar_get_preferred_width;
    wclass->get_preferred_width_for_height = nautilus_floating_bar_get_preferred_width_for_height;
    wclass->get_preferred_height = nautilus_floating_bar_get_preferred_height;
    wclass->get_preferred_height_for_width = nautilus_floating_bar_get_preferred_height_for_width;
    wclass->parent_set = nautilus_floating_bar_parent_set;

    properties[PROP_PRIMARY_LABEL] =
        g_param_spec_string ("primary-label",
                             "Bar's primary label",
                             "Primary label displayed by the bar",
                             NULL,
                             G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS);
    properties[PROP_DETAILS_LABEL] =
        g_param_spec_string ("details-label",
                             "Bar's details label",
                             "Details label displayed by the bar",
                             NULL,
                             G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS);
    properties[PROP_SHOW_SPINNER] =
        g_param_spec_boolean ("show-spinner",
                              "Show spinner",
                              "Whether a spinner should be shown in the floating bar",
                              FALSE,
                              G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

    signals[ACTION] =
        g_signal_new ("action",
                      G_TYPE_FROM_CLASS (klass),
                      G_SIGNAL_RUN_LAST,
                      0, NULL, NULL,
                      g_cclosure_marshal_VOID__INT,
                      G_TYPE_NONE, 1,
                      G_TYPE_INT);

    g_object_class_install_properties (oclass, NUM_PROPERTIES, properties);
487 488 489
}

void
490
nautilus_floating_bar_set_primary_label (NautilusFloatingBar *self,
491
                                         const gchar         *label)
492
{
493
    if (g_strcmp0 (self->primary_label, label) != 0)
494
    {
495 496
        g_free (self->primary_label);
        self->primary_label = g_strdup (label);
497

498
        g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PRIMARY_LABEL]);
499

500 501
        update_labels (self);
    }
502 503 504 505
}

void
nautilus_floating_bar_set_details_label (NautilusFloatingBar *self,
506
                                         const gchar         *label)
507
{
508
    if (g_strcmp0 (self->details_label, label) != 0)
509
    {
510 511
        g_free (self->details_label);
        self->details_label = g_strdup (label);
512

513
        g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DETAILS_LABEL]);
514

515 516
        update_labels (self);
    }
517 518
}

519 520
void
nautilus_floating_bar_set_labels (NautilusFloatingBar *self,
521 522
                                  const gchar         *primary_label,
                                  const gchar         *details_label)
523
{
524 525
    nautilus_floating_bar_set_primary_label (self, primary_label);
    nautilus_floating_bar_set_details_label (self, details_label);
526 527
}

528 529
void
nautilus_floating_bar_set_show_spinner (NautilusFloatingBar *self,
530
                                        gboolean             show_spinner)
531
{
532
    if (self->show_spinner != show_spinner)
533
    {
534 535
        self->show_spinner = show_spinner;
        gtk_widget_set_visible (self->spinner,
536 537 538 539
                                show_spinner);

        g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SHOW_SPINNER]);
    }
540 541
}

542
GtkWidget *
543
nautilus_floating_bar_new (const gchar *primary_label,
544 545
                           const gchar *details_label,
                           gboolean     show_spinner)
546
{
547 548 549 550 551 552 553
    return g_object_new (NAUTILUS_TYPE_FLOATING_BAR,
                         "primary-label", primary_label,
                         "details-label", details_label,
                         "show-spinner", show_spinner,
                         "orientation", GTK_ORIENTATION_HORIZONTAL,
                         "spacing", 8,
                         NULL);
554
}
555 556 557

void
nautilus_floating_bar_add_action (NautilusFloatingBar *self,
558 559
                                  const gchar         *icon_name,
                                  gint                 action_id)
560
{
561 562
    GtkWidget *button;
    GtkStyleContext *context;
563

564 565 566 567 568 569 570
    button = gtk_button_new_from_icon_name (icon_name, GTK_ICON_SIZE_MENU);
    context = gtk_widget_get_style_context (button);
    gtk_style_context_add_class (context, "circular");
    gtk_style_context_add_class (context, "flat");
    gtk_widget_set_valign (button, GTK_ALIGN_CENTER);
    gtk_box_pack_end (GTK_BOX (self), button, FALSE, FALSE, 0);
    gtk_widget_show (button);
571

572 573
    g_object_set_data (G_OBJECT (button), "action-id",
                       GINT_TO_POINTER (action_id));
574

575 576
    g_signal_connect (button, "clicked",
                      G_CALLBACK (action_button_clicked_cb), self);
577

578
    self->is_interactive = TRUE;
579
}
580 581 582 583

void
nautilus_floating_bar_cleanup_actions (NautilusFloatingBar *self)
{
584 585 586
    GtkWidget *widget;
    GList *children, *l;
    gpointer data;
587

588 589
    children = gtk_container_get_children (GTK_CONTAINER (self));
    l = children;
590

591 592 593 594 595
    while (l != NULL)
    {
        widget = l->data;
        data = g_object_get_data (G_OBJECT (widget), "action-id");
        l = l->next;
596

597 598 599 600 601 602
        if (data != NULL)
        {
            /* destroy this */
            gtk_widget_destroy (widget);
        }
    }
603

604
    g_list_free (children);
605

606
    self->is_interactive = FALSE;
607
}