empathy-ui-utils.c 33.9 KB
Newer Older
1 2
/*
 * Copyright (C) 2002-2007 Imendio AB
3
 * Copyright (C) 2007-2010 Collabora Ltd.
4 5 6 7 8 9 10 11 12 13 14 15 16
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program 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
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program; if not, write to the
17 18 19
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA  02110-1301  USA
 *
20 21 22
 * Authors: Mikael Hallendal <micke@imendio.com>
 *          Richard Hult <richard@imendio.com>
 *          Martyn Russell <martyn@imendio.com>
23
 *          Xavier Claessens <xclaesse@gmail.com>
24
 *          Jonny Lamb <jonny.lamb@collabora.co.uk>
25
 *          Travis Reitter <travis.reitter@collabora.co.uk>
26
 *
27 28 29 30 31
 *          Part of this file is copied from GtkSourceView (gtksourceiter.c):
 *          Paolo Maggi
 *          Jeroen Zwartepoorte
 */

32
#include "config.h"
33
#include "empathy-ui-utils.h"
34

35 36
#include <X11/Xatom.h>
#include <gdk/gdkx.h>
37
#include <glib/gi18n-lib.h>
38
#include <gio/gdesktopappinfo.h>
39
#include <tp-account-widgets/tpaw-live-search.h>
40
#include <tp-account-widgets/tpaw-pixbuf-utils.h>
41
#include <tp-account-widgets/tpaw-utils.h>
42

43
#include "empathy-ft-factory.h"
44
#include "empathy-images.h"
45
#include "empathy-utils.h"
46

47
#define DEBUG_FLAG EMPATHY_DEBUG_OTHER
48
#include "empathy-debug.h"
49

50 51
void
empathy_gtk_init (void)
52
{
53
  static gboolean initialized = FALSE;
54

55 56
  if (initialized)
    return;
57

58
  empathy_init ();
59

60 61
  gtk_icon_theme_append_search_path (gtk_icon_theme_get_default (),
      PKGDATADIR G_DIR_SEPARATOR_S "icons");
62

63 64 65 66 67 68 69
  /* Add icons from source dir if available */
  if (g_getenv ("EMPATHY_SRCDIR") != NULL)
    {
      gchar *path;

      path = g_build_filename (g_getenv ("EMPATHY_SRCDIR"), "data",
          "icons", "local-copy", NULL);
70

71 72
      if (g_file_test (path, G_FILE_TEST_EXISTS))
        gtk_icon_theme_append_search_path (gtk_icon_theme_get_default (), path);
73

74 75 76 77
      g_free (path);
    }

  initialized = TRUE;
78 79
}

80
const gchar *
81
empathy_icon_name_for_presence (TpConnectionPresenceType presence)
82
{
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
  switch (presence)
    {
      case TP_CONNECTION_PRESENCE_TYPE_AVAILABLE:
        return EMPATHY_IMAGE_AVAILABLE;
      case TP_CONNECTION_PRESENCE_TYPE_BUSY:
        return EMPATHY_IMAGE_BUSY;
      case TP_CONNECTION_PRESENCE_TYPE_AWAY:
        return EMPATHY_IMAGE_AWAY;
      case TP_CONNECTION_PRESENCE_TYPE_EXTENDED_AWAY:
        if (gtk_icon_theme_has_icon (gtk_icon_theme_get_default (),
                   EMPATHY_IMAGE_EXT_AWAY))
          return EMPATHY_IMAGE_EXT_AWAY;

        /* The 'extended-away' icon is not an official one so we fallback to
         * idle if it's not implemented */
        return EMPATHY_IMAGE_IDLE;
      case TP_CONNECTION_PRESENCE_TYPE_HIDDEN:
        if (gtk_icon_theme_has_icon (gtk_icon_theme_get_default (),
                   EMPATHY_IMAGE_HIDDEN))
          return EMPATHY_IMAGE_HIDDEN;

        /* The 'hidden' icon is not an official one so we fallback to offline if
         * it's not implemented */
        return EMPATHY_IMAGE_OFFLINE;
      case TP_CONNECTION_PRESENCE_TYPE_OFFLINE:
      case TP_CONNECTION_PRESENCE_TYPE_ERROR:
        return EMPATHY_IMAGE_OFFLINE;
      case TP_CONNECTION_PRESENCE_TYPE_UNKNOWN:
        return EMPATHY_IMAGE_PENDING;
      case TP_CONNECTION_PRESENCE_TYPE_UNSET:
      default:
        return NULL;
    }

  return NULL;
118 119
}

120
const gchar *
121
empathy_icon_name_for_contact (EmpathyContact *contact)
122
{
123
  TpConnectionPresenceType presence;
124

125 126
  g_return_val_if_fail (EMPATHY_IS_CONTACT (contact),
      EMPATHY_IMAGE_OFFLINE);
127

128 129
  presence = empathy_contact_get_presence (contact);
  return empathy_icon_name_for_presence (presence);
130 131
}

132 133 134
const gchar *
empathy_icon_name_for_individual (FolksIndividual *individual)
{
135 136
  FolksPresenceType folks_presence;
  TpConnectionPresenceType presence;
137

138 139 140
  folks_presence = folks_presence_details_get_presence_type (
      FOLKS_PRESENCE_DETAILS (individual));
  presence = empathy_folks_presence_type_to_tp (folks_presence);
141

142
  return empathy_icon_name_for_presence (presence);
143 144
}

145
const gchar *
146
empathy_protocol_name_for_contact (EmpathyContact *contact)
147
{
148
  TpAccount *account;
149

150
  g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
151

152 153 154
  account = empathy_contact_get_account (contact);
  if (account == NULL)
    return NULL;
155

156
  return tp_account_get_icon_name (account);
157 158
}

159 160 161 162 163
struct SizeData
{
  gint width;
  gint height;
  gboolean preserve_aspect_ratio;
164 165
};

166 167
static void
pixbuf_from_avatar_size_prepared_cb (GdkPixbufLoader *loader,
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
    int width,
    int height,
    struct SizeData *data)
{
  g_return_if_fail (width > 0 && height > 0);

  if (data->preserve_aspect_ratio && (data->width > 0 || data->height > 0))
    {
      if (data->width < 0)
        {
          width = width * (double) data->height / (gdouble) height;
          height = data->height;
        }
      else if (data->height < 0)
        {
          height = height * (double) data->width / (double) width;
          width = data->width;
        }
      else if ((double) height * (double) data->width >
           (double) width * (double) data->height)
        {
          width = 0.5 + (double) width * (double) data->height / (double) height;
          height = data->height;
        }
      else
        {
          height = 0.5 + (double) height * (double) data->width / (double) width;
          width = data->width;
        }
    }
  else
    {
      if (data->width > 0)
        width = data->width;

      if (data->height > 0)
        height = data->height;
    }

  gdk_pixbuf_loader_set_size (loader, width, height);
208 209
}

210 211 212
static void
empathy_avatar_pixbuf_roundify (GdkPixbuf *pixbuf)
{
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250
  gint width, height, rowstride;
  guchar *pixels;

  width = gdk_pixbuf_get_width (pixbuf);
  height = gdk_pixbuf_get_height (pixbuf);
  rowstride = gdk_pixbuf_get_rowstride (pixbuf);
  pixels = gdk_pixbuf_get_pixels (pixbuf);

  if (width < 6 || height < 6)
    return;

  /* Top left */
  pixels[3] = 0;
  pixels[7] = 0x80;
  pixels[11] = 0xC0;
  pixels[rowstride + 3] = 0x80;
  pixels[rowstride * 2 + 3] = 0xC0;

  /* Top right */
  pixels[width * 4 - 1] = 0;
  pixels[width * 4 - 5] = 0x80;
  pixels[width * 4 - 9] = 0xC0;
  pixels[rowstride + (width * 4) - 1] = 0x80;
  pixels[(2 * rowstride) + (width * 4) - 1] = 0xC0;

  /* Bottom left */
  pixels[(height - 1) * rowstride + 3] = 0;
  pixels[(height - 1) * rowstride + 7] = 0x80;
  pixels[(height - 1) * rowstride + 11] = 0xC0;
  pixels[(height - 2) * rowstride + 3] = 0x80;
  pixels[(height - 3) * rowstride + 3] = 0xC0;

  /* Bottom right */
  pixels[height * rowstride - 1] = 0;
  pixels[(height - 1) * rowstride - 1] = 0x80;
  pixels[(height - 2) * rowstride - 1] = 0xC0;
  pixels[height * rowstride - 5] = 0x80;
  pixels[height * rowstride - 9] = 0xC0;
251 252 253 254 255
}

static gboolean
empathy_gdk_pixbuf_is_opaque (GdkPixbuf *pixbuf)
{
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281
  gint height, rowstride, i;
  guchar *pixels;
  guchar *row;

  height = gdk_pixbuf_get_height (pixbuf);
  rowstride = gdk_pixbuf_get_rowstride (pixbuf);
  pixels = gdk_pixbuf_get_pixels (pixbuf);

  row = pixels;
  for (i = 3; i < rowstride; i+=4)
    if (row[i] < 0xfe)
      return FALSE;

  for (i = 1; i < height - 1; i++)
    {
      row = pixels + (i*rowstride);
      if (row[3] < 0xfe || row[rowstride-1] < 0xfe)
        return FALSE;
    }

  row = pixels + ((height-1) * rowstride);
  for (i = 3; i < rowstride; i+=4)
    if (row[i] < 0xfe)
      return FALSE;

  return TRUE;
282 283
}

284
static GdkPixbuf *
285
pixbuf_round_corners (GdkPixbuf *pixbuf)
286
{
287
  GdkPixbuf *result;
288 289 290

  if (!gdk_pixbuf_get_has_alpha (pixbuf))
    {
291
      result = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
292 293 294 295 296 297
          gdk_pixbuf_get_width (pixbuf),
          gdk_pixbuf_get_height (pixbuf));

      gdk_pixbuf_copy_area (pixbuf, 0, 0,
          gdk_pixbuf_get_width (pixbuf),
          gdk_pixbuf_get_height (pixbuf),
298
          result,
299 300 301 302
          0, 0);
    }
  else
    {
303
      result = g_object_ref (pixbuf);
304 305
    }

306 307
  if (empathy_gdk_pixbuf_is_opaque (result))
    empathy_avatar_pixbuf_roundify (result);
308

309 310 311 312 313 314 315 316 317 318 319
  return result;
}

static GdkPixbuf *
avatar_pixbuf_from_loader (GdkPixbufLoader *loader)
{
  GdkPixbuf *pixbuf;

  pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);

  return pixbuf_round_corners (pixbuf);
320 321
}

322
static GdkPixbuf *
323
empathy_pixbuf_from_avatar_scaled (EmpathyAvatar *avatar,
324 325
    gint width,
    gint height)
326
{
327 328 329 330
  GdkPixbuf *pixbuf;
  GdkPixbufLoader *loader;
  struct SizeData data;
  GError *error = NULL;
331

332 333
  if (!avatar)
    return NULL;
334

335 336 337
  data.width = width;
  data.height = height;
  data.preserve_aspect_ratio = TRUE;
338

339
  loader = gdk_pixbuf_loader_new ();
340

341 342
  g_signal_connect (loader, "size-prepared",
      G_CALLBACK (pixbuf_from_avatar_size_prepared_cb), &data);
343

344 345 346 347 348 349 350 351 352 353 354 355 356 357
  if (avatar->len == 0)
    {
      g_warning ("Avatar has 0 length");
      return NULL;
    }
  else if (!gdk_pixbuf_loader_write (loader, avatar->data, avatar->len, &error))
    {
      g_warning ("Couldn't write avatar image:%p with "
          "length:%" G_GSIZE_FORMAT " to pixbuf loader: %s",
          avatar->data, avatar->len, error->message);

      g_error_free (error);
      return NULL;
    }
358

359 360
  gdk_pixbuf_loader_close (loader, NULL);
  pixbuf = avatar_pixbuf_from_loader (loader);
361

362
  g_object_unref (loader);
363

364
  return pixbuf;
365 366 367
}

GdkPixbuf *
368
empathy_pixbuf_avatar_from_contact_scaled (EmpathyContact *contact,
369 370
    gint width,
    gint height)
371
{
372
  EmpathyAvatar *avatar;
373

374
  g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
375

376
  avatar = empathy_contact_get_avatar (contact);
377

378
  return empathy_pixbuf_from_avatar_scaled (avatar, width, height);
379
}
380

381 382 383 384 385 386
typedef struct
{
  GSimpleAsyncResult *result;
  guint width;
  guint height;
  GCancellable *cancellable;
387 388
} PixbufAvatarFromIndividualClosure;

389
static PixbufAvatarFromIndividualClosure *
390 391 392 393 394 395 396 397 398 399 400
pixbuf_avatar_from_individual_closure_new (FolksIndividual *individual,
    GSimpleAsyncResult *result,
    gint width,
    gint height,
    GCancellable *cancellable)
{
  PixbufAvatarFromIndividualClosure *closure;

  g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
  g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);

401
  closure = g_slice_new0 (PixbufAvatarFromIndividualClosure);
402 403 404 405 406 407 408 409
  closure->result = g_object_ref (result);
  closure->width = width;
  closure->height = height;

  if (cancellable != NULL)
    closure->cancellable = g_object_ref (cancellable);

  return closure;
410 411
}

412 413
static void
pixbuf_avatar_from_individual_closure_free (
414
    PixbufAvatarFromIndividualClosure *closure)
415
{
416 417
  g_clear_object (&closure->cancellable);
  g_object_unref (closure->result);
418
  g_slice_free (PixbufAvatarFromIndividualClosure, closure);
419 420
}

421 422 423 424 425 426 427
/**
 * @pixbuf: (transfer all)
 *
 * Return: (transfer all)
 */
static GdkPixbuf *
transform_pixbuf (GdkPixbuf *pixbuf)
428
{
429
  GdkPixbuf *result;
430

431 432
  result = pixbuf_round_corners (pixbuf);
  g_object_unref (pixbuf);
433

434
  return result;
435 436 437
}

static void
438 439 440
avatar_icon_load_cb (GObject *object,
    GAsyncResult *result,
    gpointer user_data)
441
{
442 443 444 445
  GLoadableIcon *icon = G_LOADABLE_ICON (object);
  PixbufAvatarFromIndividualClosure *closure = user_data;
  GInputStream *stream;
  GError *error = NULL;
446 447
  GdkPixbuf *pixbuf;
  GdkPixbuf *final_pixbuf;
448

449 450 451 452 453 454 455
  stream = g_loadable_icon_load_finish (icon, result, NULL, &error);
  if (error != NULL)
    {
      DEBUG ("Failed to open avatar stream: %s", error->message);
      g_simple_async_result_set_from_error (closure->result, error);
      goto out;
    }
456

457 458
  pixbuf = gdk_pixbuf_new_from_stream_at_scale (stream,
      closure->width, closure->height, TRUE, closure->cancellable, &error);
459

460
  g_object_unref (stream);
461

462 463 464 465 466 467
  if (pixbuf == NULL)
    {
      DEBUG ("Failed to read avatar: %s", error->message);
      g_simple_async_result_set_from_error (closure->result, error);
      goto out;
    }
468

469
  final_pixbuf = transform_pixbuf (pixbuf);
470

471 472 473
  /* Pass ownership of final_pixbuf to the result */
  g_simple_async_result_set_op_res_gpointer (closure->result,
      final_pixbuf, g_object_unref);
474 475

out:
476
  g_simple_async_result_complete (closure->result);
477

478 479
  g_clear_error (&error);
  pixbuf_avatar_from_individual_closure_free (closure);
480 481 482
}

void
Travis Reitter's avatar
Travis Reitter committed
483
empathy_pixbuf_avatar_from_individual_scaled_async (
484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519
    FolksIndividual *individual,
    gint width,
    gint height,
    GCancellable *cancellable,
    GAsyncReadyCallback callback,
    gpointer user_data)
{
  GLoadableIcon *avatar_icon;
  GSimpleAsyncResult *result;
  PixbufAvatarFromIndividualClosure *closure;

  result = g_simple_async_result_new (G_OBJECT (individual),
      callback, user_data, empathy_pixbuf_avatar_from_individual_scaled_async);

  avatar_icon = folks_avatar_details_get_avatar (
      FOLKS_AVATAR_DETAILS (individual));

  if (avatar_icon == NULL)
    {
      g_simple_async_result_set_error (result, G_IO_ERROR,
        G_IO_ERROR_NOT_FOUND, "no avatar found");

      g_simple_async_result_complete (result);
      g_object_unref (result);
      return;
    }

  closure = pixbuf_avatar_from_individual_closure_new (individual, result,
      width, height, cancellable);

  g_return_if_fail (closure != NULL);

  g_loadable_icon_load_async (avatar_icon, width, cancellable,
      avatar_icon_load_cb, closure);

  g_object_unref (result);
520 521
}

522
/* Return a ref on the GdkPixbuf */
523 524
GdkPixbuf *
empathy_pixbuf_avatar_from_individual_scaled_finish (
525 526 527
    FolksIndividual *individual,
    GAsyncResult *result,
    GError **error)
528
{
529 530 531
  GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result);
  gboolean result_valid;
  GdkPixbuf *pixbuf;
532

533 534
  g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
  g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (simple), NULL);
535

536 537
  if (g_simple_async_result_propagate_error (simple, error))
    return NULL;
538

539 540 541
  result_valid = g_simple_async_result_is_valid (result,
      G_OBJECT (individual),
      empathy_pixbuf_avatar_from_individual_scaled_async);
542

543 544 545 546
  g_return_val_if_fail (result_valid, NULL);

  pixbuf = g_simple_async_result_get_op_res_gpointer (simple);
  return pixbuf != NULL ? g_object_ref (pixbuf) : NULL;
547 548
}

549 550
GdkPixbuf *
empathy_pixbuf_contact_status_icon (EmpathyContact *contact,
551
    gboolean show_protocol)
552
{
553 554 555
  const gchar *icon_name;

  g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
556

557
  icon_name = empathy_icon_name_for_contact (contact);
558

559 560
  if (icon_name == NULL)
    return NULL;
561

562 563
  return empathy_pixbuf_contact_status_icon_with_icon_name (contact,
      icon_name, show_protocol);
564 565
}

566 567 568 569 570
static GdkPixbuf * empathy_pixbuf_protocol_from_contact_scaled (
    EmpathyContact *contact,
    gint width,
    gint height);

571 572
GdkPixbuf *
empathy_pixbuf_contact_status_icon_with_icon_name (EmpathyContact *contact,
573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588
    const gchar *icon_name,
    gboolean show_protocol)
{
  GdkPixbuf *pix_status;
  GdkPixbuf *pix_protocol;
  gchar *icon_filename;
  gint height, width;
  gint numerator, denominator;

  g_return_val_if_fail (EMPATHY_IS_CONTACT (contact) ||
      (show_protocol == FALSE), NULL);
  g_return_val_if_fail (icon_name != NULL, NULL);

  numerator = 3;
  denominator = 4;

589
  icon_filename = tpaw_filename_from_icon_name (icon_name,
590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631
      GTK_ICON_SIZE_MENU);

  if (icon_filename == NULL)
    {
      DEBUG ("icon name: %s could not be found\n", icon_name);
      return NULL;
    }

  pix_status = gdk_pixbuf_new_from_file (icon_filename, NULL);

  if (pix_status == NULL)
    {
      DEBUG ("Could not open icon %s\n", icon_filename);
      g_free (icon_filename);
      return NULL;
    }

  g_free (icon_filename);

  if (!show_protocol)
    return pix_status;

  height = gdk_pixbuf_get_height (pix_status);
  width = gdk_pixbuf_get_width (pix_status);

  pix_protocol = empathy_pixbuf_protocol_from_contact_scaled (contact,
      width * numerator / denominator,
      height * numerator / denominator);

  if (pix_protocol == NULL)
    return pix_status;

  gdk_pixbuf_composite (pix_protocol, pix_status,
      0, height - height * numerator / denominator,
      width * numerator / denominator, height * numerator / denominator,
      0, height - height * numerator / denominator,
      1, 1,
      GDK_INTERP_BILINEAR, 255);

  g_object_unref (pix_protocol);

  return pix_status;
632 633
}

634
static GdkPixbuf *
635
empathy_pixbuf_protocol_from_contact_scaled (EmpathyContact *contact,
636 637
    gint width,
    gint height)
638
{
639 640 641 642 643
  TpAccount *account;
  gchar *filename;
  GdkPixbuf *pixbuf = NULL;

  g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
644

645
  account = empathy_contact_get_account (contact);
646
  filename = tpaw_filename_from_icon_name (
647
      tp_account_get_icon_name (account), GTK_ICON_SIZE_MENU);
648

649 650 651 652 653
  if (filename != NULL)
    {
      pixbuf = gdk_pixbuf_new_from_file_at_size (filename, width, height, NULL);
      g_free (filename);
    }
654

655
  return pixbuf;
656 657
}

658
void
659
empathy_url_show (GtkWidget *parent,
660
      const char *url)
661
{
662 663
  gchar  *real_url;
  GError *error = NULL;
664

665 666
  g_return_if_fail (parent == NULL || GTK_IS_WIDGET (parent));
  g_return_if_fail (url != NULL);
667

668
  real_url = tpaw_make_absolute_url (url);
669 670 671 672 673 674 675

  gtk_show_uri (parent ? gtk_widget_get_screen (parent) : NULL, real_url,
      gtk_get_current_event_time (), &error);

  if (error)
    {
      GtkWidget *dialog;
676

677 678 679
      dialog = gtk_message_dialog_new (NULL, 0,
          GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
          _("Unable to open URI"));
680

681 682
      gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
          "%s", error->message);
683

684 685
      g_signal_connect (dialog, "response",
            G_CALLBACK (gtk_widget_destroy), NULL);
686

687
      gtk_window_present (GTK_WINDOW (dialog));
688

689 690
      g_clear_error (&error);
    }
691

692
  g_free (real_url);
693 694
}

695
void
696 697
empathy_send_file (EmpathyContact *contact,
    GFile *file)
698
{
699 700 701
  EmpathyFTFactory *factory;
  GtkRecentManager *manager;
  gchar *uri;
702

703 704
  g_return_if_fail (EMPATHY_IS_CONTACT (contact));
  g_return_if_fail (G_IS_FILE (file));
705

706
  factory = empathy_ft_factory_dup_singleton ();
707

708 709
  empathy_ft_factory_new_transfer_outgoing (factory, contact, file,
      empathy_get_current_action_time ());
710

711 712 713 714
  uri = g_file_get_uri (file);
  manager = gtk_recent_manager_get_default ();
  gtk_recent_manager_add_item (manager, uri);
  g_free (uri);
715

716
  g_object_unref (factory);
717 718
}

719
void
720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749
empathy_send_file_from_uri_list (EmpathyContact *contact,
    const gchar *uri_list)
{
  const gchar *nl;
  GFile *file;

  /* Only handle a single file for now. It would be wicked cool to be
     able to do multiple files, offering to zip them or whatever like
     nautilus-sendto does. Note that text/uri-list is defined to have
     each line terminated by \r\n, but we can be tolerant of applications
     that only use \n or don't terminate single-line entries.
  */
  nl = strstr (uri_list, "\r\n");
  if (!nl)
    nl = strchr (uri_list, '\n');

  if (nl)
    {
      gchar *uri = g_strndup (uri_list, nl - uri_list);
      file = g_file_new_for_uri (uri);
      g_free (uri);
    }
  else
    {
      file = g_file_new_for_uri (uri_list);
    }

  empathy_send_file (contact, file);

  g_object_unref (file);
750 751
}

752
static void
753 754 755
file_manager_send_file_response_cb (GtkDialog *widget,
    gint response_id,
    EmpathyContact *contact)
756
{
757
  GFile *file;
758

759 760 761
  if (response_id == GTK_RESPONSE_OK)
    {
      file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (widget));
762

763
      empathy_send_file (contact, file);
764

765 766
      g_object_unref (file);
    }
767

768 769
  g_object_unref (contact);
  gtk_widget_destroy (GTK_WIDGET (widget));
770 771
}

772 773
static gboolean
filter_cb (const GtkFileFilterInfo *filter_info,
774
    gpointer data)
775
{
776 777
  /* filter out socket files */
  return tp_strdiff (filter_info->mime_type, "inode/socket");
778 779 780 781 782
}

static GtkFileFilter *
create_file_filter (void)
{
783
  GtkFileFilter *filter;
784

785
  filter = gtk_file_filter_new ();
786

787 788
  gtk_file_filter_add_custom (filter, GTK_FILE_FILTER_MIME_TYPE, filter_cb,
    NULL, NULL);
789

790
  return filter;
791 792
}

793
void
794
empathy_send_file_with_file_chooser (EmpathyContact *contact)
795
{
796 797 798 799 800 801
  GtkWidget *widget;
  GtkWidget *button;

  g_return_if_fail (EMPATHY_IS_CONTACT (contact));

  DEBUG ("Creating selection file chooser");
802

803 804 805 806
  widget = gtk_file_chooser_dialog_new (_("Select a file"), NULL,
      GTK_FILE_CHOOSER_ACTION_OPEN,
      GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
      NULL);
807

808 809 810 811 812 813
  /* send button */
  button = gtk_button_new_with_mnemonic (_("_Send"));
  gtk_button_set_image (GTK_BUTTON (button),
      gtk_image_new_from_icon_name (EMPATHY_IMAGE_DOCUMENT_SEND,
        GTK_ICON_SIZE_BUTTON));
  gtk_widget_show (button);
814

815 816
  gtk_dialog_add_action_widget (GTK_DIALOG (widget), button,
      GTK_RESPONSE_OK);
817

818 819 820
  gtk_widget_set_can_default (button, TRUE);
  gtk_dialog_set_default_response (GTK_DIALOG (widget),
      GTK_RESPONSE_OK);
821

822
  gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (widget), FALSE);
823

824 825
  gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (widget),
      g_get_home_dir ());
826

827 828
  gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (widget),
      create_file_filter ());
829

830 831
  g_signal_connect (widget, "response",
      G_CALLBACK (file_manager_send_file_response_cb), g_object_ref (contact));
832

833
  gtk_widget_show (widget);
834
}
835

836 837
static void
file_manager_receive_file_response_cb (GtkDialog *dialog,
838 839
    GtkResponseType response,
    EmpathyFTHandler *handler)
840
{
841 842
  EmpathyFTFactory *factory;
  GFile *file;
843

844 845 846 847 848 849
  if (response == GTK_RESPONSE_OK)
    {
      GFile *parent;
      GFileInfo *info;
      guint64 free_space, file_size;
      GError *error = NULL;
850

851 852 853 854
      file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
      parent = g_file_get_parent (file);
      info = g_file_query_filesystem_info (parent,
          G_FILE_ATTRIBUTE_FILESYSTEM_FREE, NULL, &error);
855

856
      g_object_unref (parent);
857

858 859 860
      if (error != NULL)
        {
          g_warning ("Error: %s", error->message);
861

862 863 864
          g_object_unref (file);
          return;
        }
865

866 867 868
      free_space = g_file_info_get_attribute_uint64 (info,
          G_FILE_ATTRIBUTE_FILESYSTEM_FREE);
      file_size = empathy_ft_handler_get_total_bytes (handler);
869

870
      g_object_unref (info);
871

872 873 874 875 876 877 878
      if (file_size > free_space)
        {
          GtkWidget *message = gtk_message_dialog_new (GTK_WINDOW (dialog),
            GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR,
            GTK_BUTTONS_CLOSE,
            _("Insufficient free space to save file"));
          char *file_size_str, *free_space_str;
879

880 881
          file_size_str = g_format_size (file_size);
          free_space_str = g_format_size (free_space);
882

883 884 885 886 887 888
          gtk_message_dialog_format_secondary_text (
              GTK_MESSAGE_DIALOG (message),
              _("%s of free space are required to save this "
                "file, but only %s is available. Please "
                "choose another location."),
            file_size_str, free_space_str);
889

890
          gtk_dialog_run (GTK_DIALOG (message));
891

892 893 894
          g_free (file_size_str);
          g_free (free_space_str);
          gtk_widget_destroy (message);
895

896
          g_object_unref (file);
897

898 899
          return;
        }
900

901
      factory = empathy_ft_factory_dup_singleton ();
902

903 904
      empathy_ft_factory_set_destination_for_incoming_handler (
          factory, handler, file);
905

906 907 908 909 910 911 912 913 914 915
      g_object_unref (factory);
      g_object_unref (file);
    }
  else
    {
      /* unref the handler, as we dismissed the file chooser,
       * and refused the transfer.
       */
      g_object_unref (handler);
    }
916

917
  gtk_widget_destroy (GTK_WIDGET (dialog));
918 919 920 921 922
}

void
empathy_receive_file_with_file_chooser (EmpathyFTHandler *handler)
{
923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957
  GtkWidget *widget;
  const gchar *dir;
  EmpathyContact *contact;
  gchar *title;

  contact = empathy_ft_handler_get_contact (handler);
  g_assert (contact != NULL);

  title = g_strdup_printf (_("Incoming file from %s"),
    empathy_contact_get_alias (contact));

  widget = gtk_file_chooser_dialog_new (title,
      NULL, GTK_FILE_CHOOSER_ACTION_SAVE,
      GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
      GTK_STOCK_SAVE, GTK_RESPONSE_OK,
      NULL);

  gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (widget),
    empathy_ft_handler_get_filename (handler));

  gtk_file_chooser_set_do_overwrite_confirmation
    (GTK_FILE_CHOOSER (widget), TRUE);

  dir = g_get_user_special_dir (G_USER_DIRECTORY_DOWNLOAD);
  if (dir == NULL)
    /* Fallback to $HOME if $XDG_DOWNLOAD_DIR is not set */
    dir = g_get_home_dir ();

  gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (widget), dir);

  g_signal_connect (widget, "response",
      G_CALLBACK (file_manager_receive_file_response_cb), handler);

  gtk_widget_show (widget);
  g_free (title);
Sjoerd Simons's avatar
Sjoerd Simons committed
958
}
959 960 961 962

void
empathy_make_color_whiter (GdkRGBA *color)
{
963
  const GdkRGBA white = { 1.0, 1.0, 1.0, 1.0 };
964

965 966 967
  color->red = (color->red + white.red) / 2;
  color->green = (color->green + white.green) / 2;
  color->blue = (color->blue + white.blue) / 2;
968
}
969 970 971

static void
menu_deactivate_cb (GtkMenu *menu,
972
  gpointer user_data)
973
{
974 975 976
  /* FIXME: we shouldn't have to disconnect the signal (bgo #641327) */
  g_signal_handlers_disconnect_by_func (menu,
         menu_deactivate_cb, user_data);
977

978
  gtk_menu_detach (menu);
979 980 981 982 983 984 985 986
}

/* Convenient function to create a GtkMenu attached to @attach_to and detach
 * it when the menu is not displayed any more. This is useful when creating a
 * context menu that we want to get rid as soon as it as been displayed. */
GtkWidget *
empathy_context_menu_new (GtkWidget *attach_to)
{
987
  GtkWidget *menu;
988

989
  menu = gtk_menu_new ();
990

991
  gtk_menu_attach_to_widget (GTK_MENU (menu), attach_to, NULL);
992

993 994 995 996 997 998 999
  /* menu is initially unowned but gtk_menu_attach_to_widget () taked its
   * floating ref. We can either wait that @attach_to releases its ref when
   * it will be destroyed (when leaving Empathy most of the time) or explicitely
   * detach the menu when it's not displayed any more.
   * We go for the latter as we don't want to keep useless menus in memory
   * during the whole lifetime of Empathy. */
  g_signal_connect (menu, "deactivate", G_CALLBACK (menu_deactivate_cb), NULL);
1000

1001
  return menu;
1002
}
1003 1004 1005 1006 1007 1008

gint64
empathy_get_current_action_time (void)
{
  return (tp_user_action_time_from_x11 (gtk_get_current_event_time ()));
}
1009

1010
/* @words = tpaw_live_search_strip_utf8_string (@text);
1011 1012 1013
 *
 * User has to pass both so we don't have to compute @words ourself each time
 * this function is called. */
1014
gboolean
1015 1016
empathy_individual_match_string (FolksIndividual *individual,
    const char *text,
1017 1018 1019
    GPtrArray *words)
{
  const gchar *str;
1020 1021 1022
  GeeSet *personas;
  GeeIterator *iter;
  gboolean retval = FALSE;
1023 1024 1025 1026

  /* check alias name */
  str = folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual));

1027
  if (tpaw_live_search_match_words (str, words))
1028 1029 1030 1031 1032
    return TRUE;

  personas = folks_individual_get_personas (individual);

  /* check contact id, remove the @server.com part */
1033 1034
  iter = gee_iterable_iterator (GEE_ITERABLE (personas));
  while (retval == FALSE && gee_iterator_next (iter))
1035
    {
1036
      FolksPersona *persona = gee_iterator_get (iter);
1037 1038
      const gchar *p;

1039 1040 1041 1042 1043 1044
      if (empathy_folks_persona_is_interesting (persona))
        {
          str = folks_persona_get_display_id (persona);

          /* Accept the persona if @text is a full prefix of his ID; that allows
           * user to find, say, a jabber contact by typing his JID. */
1045
          if (g_str_has_prefix (str, text))
1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057
            {
              retval = TRUE;
            }
          else
            {
              gchar *dup_str = NULL;
              gboolean visible;

              p = strstr (str, "@");
              if (p != NULL)
                str = dup_str = g_strndup (str, p - str);

1058
              visible = tpaw_live_search_match_words (str, words);
1059 1060 1061 1062 1063 1064
              g_free (dup_str);
              if (visible)
                retval = TRUE;
            }
        }
      g_clear_object (&persona);
1065
    }
1066
  g_clear_object (&iter);
1067 1068 1069

  /* FIXME: Add more rules here, we could check phone numbers in
   * contact's vCard for example. */
1070
  return retval;
1071
}
1072

1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123
void
empathy_launch_program (const gchar *dir,
    const gchar *name,
    const gchar *args)
{
  GdkDisplay *display;
  GError *error = NULL;
  gchar *path, *cmd;
  GAppInfo *app_info;
  GdkAppLaunchContext *context = NULL;

  /* Try to run from source directory if possible */
  path = g_build_filename (g_getenv ("EMPATHY_SRCDIR"), "src",
      name, NULL);

  if (!g_file_test (path, G_FILE_TEST_EXISTS))
    {
      g_free (path);
      path = g_build_filename (dir, name, NULL);
    }

  if (args != NULL)
    cmd = g_strconcat (path, " ", args, NULL);
  else
    cmd = g_strdup (path);

  app_info = g_app_info_create_from_commandline (cmd, NULL, 0, &error);
  if (app_info == NULL)
    {
      DEBUG ("Failed to create app info: %s", error->message);
      g_error_free (error);
      goto out;
    }

  display = gdk_display_get_default ();
  context = gdk_display_get_app_launch_context (display);

  if (!g_app_info_launch (app_info, NULL, (GAppLaunchContext *) context,
      &error))
    {
      g_warning ("Failed to launch %s: %s", name, error->message);
      g_error_free (error);
      goto out;
    }

out:
  tp_clear_object (&app_info);
  tp_clear_object (&context);
  g_free (path);
  g_free (cmd);
}
1124

1125 1126
#ifdef GDK_WINDOWING_X11

1127 1128 1129 1130 1131 1132 1133 1134 1135 1136
/* Most of the workspace manipulation code has been copied from libwnck
 * Copyright (C) 2001 Havoc Pennington
 * Copyright (C) 2005-2007 Vincent Untz
 */
static void
_wnck_activate_workspace (Screen *screen,
    int new_active_space,
    Time timestamp)
{
  Display *display;
1137 1138
  Window root;
  XEvent xev;
1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185

  display = DisplayOfScreen (screen);
  root = RootWindowOfScreen (screen);

  xev.xclient.type = ClientMessage;
  xev.xclient.serial = 0;
  xev.xclient.send_event = True;
  xev.xclient.display = display;
  xev.xclient.window = root;
  xev.xclient.message_type = gdk_x11_get_xatom_by_name ("_NET_CURRENT_DESKTOP");
  xev.xclient.format = 32;
  xev.xclient.data.l[0] = new_active_space;
  xev.xclient.data.l[1] = timestamp;
  xev.xclient.data.l[2] = 0;
  xev.xclient.data.l[3] = 0;
  xev.xclient.data.l[4] = 0;

  gdk_error_trap_push ();
  XSendEvent (display, root, False,
      SubstructureRedirectMask | SubstructureNotifyMask,
      &xev);
  XSync (display, False);
  gdk_error_trap_pop_ignored ();
}

static gboolean
_wnck_get_cardinal (Screen *screen,
    Window xwindow,
    Atom atom,
    int *val)
{
  Display *display;
  Atom type;
  int format;
  gulong nitems;
  gulong bytes_after;
  gulong *num;
  int err, result;

  display = DisplayOfScreen (screen);

  *val = 0;

  gdk_error_trap_push ();
  type = None;
  result = XGetWindowProperty (display, xwindow, atom,
      0, G_MAXLONG, False, XA_CARDINAL, &type, &format, &nitems,
Guillaume Desmottes's avatar
Guillaume Desmottes committed
1186
      &bytes_after, (void *) &num);
1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217
  err = gdk_error_trap_pop ();
  if (err != Success ||
      result != Success)
    return FALSE;

  if (type != XA_CARDINAL)
    {
      XFree (num);
      return FALSE;
    }

  *val = *num;

  XFree (num);

  return TRUE;
}

static int
window_get_workspace (Screen *xscreen,
    Window win)
{
  int number;

  if (!_wnck_get_cardinal (xscreen, win,
        gdk_x11_get_xatom_by_name ("_NET_WM_DESKTOP"), &number))
    return -1;

  return number;
}

1218 1219
#endif

1220 1221 1222 1223 1224 1225
/* Ask X to move to the desktop on which @window currently is
 * and the present @window. */
void
empathy_move_to_window_desktop (GtkWindow *window,
    guint32 timestamp)
{
1226
#ifdef GDK_WINDOWING_X11
1227 1228 1229 1230 1231 1232
  GdkScreen *screen;
  Screen *xscreen;
  GdkWindow *gdk_window;
  int workspace;

  screen = gtk_window_get_screen (window);
1233 1234 1235
  if (!GDK_IS_X11_SCREEN (screen))
    goto out;

1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247
  xscreen = gdk_x11_screen_get_xscreen (screen);
  gdk_window = gtk_widget_get_window (GTK_WIDGET (window));

  workspace = window_get_workspace (xscreen,
      gdk_x11_window_get_xid (gdk_window));
  if (workspace == -1)
    goto out;

  _wnck_activate_workspace (xscreen, workspace, timestamp);

out:
  gtk_window_present_with_time (window, timestamp);
1248
#endif
1249
}
1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282

void
empathy_set_css_provider (GtkWidget *widget)
{
  GtkCssProvider *provider;
  gchar *filename;
  GError *error = NULL;
  GdkScreen *screen;

  filename = empathy_file_lookup ("empathy.css", "data");

  provider = gtk_css_provider_new ();

  if (!gtk_css_provider_load_from_path (provider, filename, &error))
    {
      g_warning ("Failed to load css file '%s': %s", filename, error->message);
      g_error_free (error);
      goto out;
    }

  if (widget != NULL)
    screen = gtk_widget_get_screen (widget);
  else
    screen = gdk_screen_get_default ();

  gtk_style_context_add_provider_for_screen (screen,
      GTK_STYLE_PROVIDER (provider),
      GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);

out:
  g_free (filename);
  g_object_unref (provider);
}
1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309

static gboolean
launch_app_info (GAppInfo *app_info,
    GError **error)
{
  GdkAppLaunchContext *context = NULL;
  GdkDisplay *display;
  GError *err = NULL;

  display = gdk_display_get_default ();
  context = gdk_display_get_app_launch_context (display);

  if (!g_app_info_launch (app_info, NULL, (GAppLaunchContext *) context,
        &err))
    {
      DEBUG ("Failed to launch %s: %s",
          g_app_info_get_display_name (app_info), err->message);
      g_propagate_error (error, err);
      return FALSE;
    }

  tp_clear_object (&context);
  return TRUE;
}

gboolean
empathy_launch_external_app (const gchar *desktop_file,
1310
    const gchar *args,
1311 1312 1313 1314
    GError **error)
{
  GDesktopAppInfo *desktop_info;
  gboolean result;
1315
  GError *err = NULL;
1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326

  desktop_info = g_desktop_app_info_new (desktop_file);
  if (desktop_info == NULL)
    {
      DEBUG ("%s not found", desktop_file);

      g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
          "%s not found", desktop_file);
      return FALSE;
    }

1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338
  if (args == NULL)
    {
      result = launch_app_info (G_APP_INFO (desktop_info), error);
    }
  else
    {
      gchar *cmd;
      GAppInfo *app_info;

      /* glib doesn't have API to start a desktop file with args... (#637875) */
      cmd = g_strdup_printf ("%s %s", g_app_info_get_commandline (
            (GAppInfo *) desktop_info), args);
1339

1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356
      app_info = g_app_info_create_from_commandline (cmd, NULL, 0, &err);
      if (app_info == NULL)
        {
          DEBUG ("Failed to launch '%s': %s", cmd, err->message);
          g_free (cmd);
          g_object_unref (desktop_info);
          g_propagate_error (error, err);
          return FALSE;
        }

      result = launch_app_info (app_info, error);

      g_object_unref (app_info);
      g_free (cmd);
    }

  g_object_unref (desktop_info);
1357 1358
  return result;
}