gtkappchooserwidget.c 46.4 KB
Newer Older
1
/*
2
 * gtkappchooserwidget.c: an app-chooser widget
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
 *
 * Copyright (C) 2004 Novell, Inc.
 * Copyright (C) 2007, 2010 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
 * License along with the Gnome Library; see the file COPYING.LIB.  If not,
 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 * Authors: Dave Camp <dave@novell.com>
 *          Alexander Larsson <alexl@redhat.com>
 *          Cosimo Cecchi <ccecchi@redhat.com>
 */

Matthias Clasen's avatar
Matthias Clasen committed
27
#include "config.h"
28

29
#include "gtkappchooserwidget.h"
30 31 32

#include "gtkintl.h"
#include "gtkmarshalers.h"
Matthias Clasen's avatar
Matthias Clasen committed
33
#include "gtkappchooserwidget.h"
34
#include "gtkappchooserprivate.h"
Matthias Clasen's avatar
Matthias Clasen committed
35 36 37 38 39 40 41 42
#include "gtkliststore.h"
#include "gtkcellrenderertext.h"
#include "gtkcellrendererpixbuf.h"
#include "gtktreeview.h"
#include "gtktreeselection.h"
#include "gtktreemodelsort.h"
#include "gtkorientable.h"
#include "gtkscrolledwindow.h"
43 44 45 46 47

#include <string.h>
#include <glib/gi18n-lib.h>
#include <gio/gio.h>

Matthias Clasen's avatar
Matthias Clasen committed
48 49 50 51 52 53 54 55 56
/**
 * SECTION:gtkappchooserwidget
 * @Title: GtkAppChooserWidget
 * @Short_description: Application chooser widget that can be embedded in other widgets
 *
 * #GtkAppChooserWidget is a widget for selecting applications.
 * It is the main building block for #GtkAppChooserDialog. Most
 * applications only need to use the latter; but you can use
 * this widget as part of a larger widget if you have special needs.
57 58 59 60 61 62 63 64 65 66 67 68 69
 *
 * #GtkAppChooserWidget offers detailed control over what applications
 * are shown, using the
 * #GtkAppChooserWidget:show-default,
 * #GtkAppChooserWidget:show-recommended,
 * #GtkAppChooserWidget:show-fallback,
 * #GtkAppChooserWidget:show-other and
 * #GtkAppChooserWidget:show-all
 * properties. See the #GtkAppChooser documentation for more information
 * about these groups of applications.
 *
 * To keep track of the selected application, use the
 * #GtkAppChooserWidget::application-selected and #GtkAppChooserWidget::application-activated signals.
Matthias Clasen's avatar
Matthias Clasen committed
70 71
 */

72
struct _GtkAppChooserWidgetPrivate {
73 74
  GAppInfo *selected_app_info;

75 76
  gchar *content_type;
  gchar *default_text;
77 78 79 80 81 82

  guint show_default     : 1;
  guint show_recommended : 1;
  guint show_fallback    : 1;
  guint show_other       : 1;
  guint show_all         : 1;
83 84 85 86 87 88 89 90 91 92 93

  GtkWidget *program_list;
  GtkListStore *program_list_store;

  GtkCellRenderer *padding_renderer;
};

enum {
  COLUMN_APP_INFO,
  COLUMN_GICON,
  COLUMN_NAME,
94
  COLUMN_DESC,
95
  COLUMN_EXEC,
96
  COLUMN_DEFAULT,
97 98 99
  COLUMN_HEADING,
  COLUMN_HEADING_TEXT,
  COLUMN_RECOMMENDED,
100
  COLUMN_FALLBACK,
101 102 103 104 105 106 107
  NUM_COLUMNS
};


enum {
  PROP_CONTENT_TYPE = 1,
  PROP_GFILE,
108
  PROP_SHOW_DEFAULT,
109 110 111 112
  PROP_SHOW_RECOMMENDED,
  PROP_SHOW_FALLBACK,
  PROP_SHOW_OTHER,
  PROP_SHOW_ALL,
113
  PROP_DEFAULT_TEXT,
114 115 116 117 118 119
  N_PROPERTIES
};

enum {
  SIGNAL_APPLICATION_SELECTED,
  SIGNAL_APPLICATION_ACTIVATED,
120
  SIGNAL_POPULATE_POPUP,
121 122 123 124 125
  N_SIGNALS
};

static guint signals[N_SIGNALS] = { 0, };

126
static void gtk_app_chooser_widget_iface_init (GtkAppChooserIface *iface);
127

128
G_DEFINE_TYPE_WITH_CODE (GtkAppChooserWidget, gtk_app_chooser_widget, GTK_TYPE_BOX,
Matthias Clasen's avatar
Matthias Clasen committed
129 130
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_APP_CHOOSER,
                                                gtk_app_chooser_widget_iface_init));
131 132

static void
133
refresh_and_emit_app_selected (GtkAppChooserWidget *self,
Matthias Clasen's avatar
Matthias Clasen committed
134
                               GtkTreeSelection    *selection)
135 136 137 138 139 140 141
{
  GtkTreeModel *model;
  GtkTreeIter iter;
  GAppInfo *info = NULL;
  gboolean should_emit = FALSE;

  if (gtk_tree_selection_get_selected (selection, &model, &iter))
Matthias Clasen's avatar
Matthias Clasen committed
142
    gtk_tree_model_get (model, &iter, COLUMN_APP_INFO, &info, -1);
143 144 145 146

  if (info == NULL)
    return;

147
  if (self->priv->selected_app_info)
148 149
    {
      if (!g_app_info_equal (self->priv->selected_app_info, info))
Matthias Clasen's avatar
Matthias Clasen committed
150 151 152
        {
          should_emit = TRUE;
          g_object_unref (self->priv->selected_app_info);
153

Matthias Clasen's avatar
Matthias Clasen committed
154 155
          self->priv->selected_app_info = info;
        }
156 157 158 159 160 161 162 163 164
    }
  else
    {
      should_emit = TRUE;
      self->priv->selected_app_info = info;
    }

  if (should_emit)
    g_signal_emit (self, signals[SIGNAL_APPLICATION_SELECTED], 0,
Matthias Clasen's avatar
Matthias Clasen committed
165
                   self->priv->selected_app_info);
166 167
}

168 169
static GAppInfo *
get_app_info_for_event (GtkAppChooserWidget *self,
Matthias Clasen's avatar
Matthias Clasen committed
170
                        GdkEventButton      *event)
171 172 173 174 175 176 177 178
{
  GtkTreePath *path = NULL;
  GtkTreeIter iter;
  GtkTreeModel *model;
  GAppInfo *info;
  gboolean recommended;

  if (!gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (self->priv->program_list),
Matthias Clasen's avatar
Matthias Clasen committed
179 180 181
                                      event->x, event->y,
                                      &path,
                                      NULL, NULL, NULL))
182 183 184 185 186 187 188 189 190 191 192 193
    return NULL;

  model = gtk_tree_view_get_model (GTK_TREE_VIEW (self->priv->program_list));

  if (!gtk_tree_model_get_iter (model, &iter, path))
    {
      gtk_tree_path_free (path);
      return NULL;
    }

  /* we only allow interaction with recommended applications */
  gtk_tree_model_get (model, &iter,
Matthias Clasen's avatar
Matthias Clasen committed
194 195 196
                      COLUMN_APP_INFO, &info,
                      COLUMN_RECOMMENDED, &recommended,
                      -1);
197 198 199 200 201 202 203 204

  if (!recommended)
    g_clear_object (&info);

  return info;
}

static gboolean
Matthias Clasen's avatar
Matthias Clasen committed
205 206 207
widget_button_press_event_cb (GtkWidget      *widget,
                              GdkEventButton *event,
                              gpointer        user_data)
208 209 210
{
  GtkAppChooserWidget *self = user_data;

211
  if (event->button == GDK_BUTTON_SECONDARY && event->type == GDK_BUTTON_PRESS)
212 213 214 215 216 217 218 219 220
    {
      GAppInfo *info;
      GtkWidget *menu;
      GList *children;
      gint n_children;

      info = get_app_info_for_event (self, event);

      if (info == NULL)
Matthias Clasen's avatar
Matthias Clasen committed
221
        return FALSE;
222 223 224 225

      menu = gtk_menu_new ();

      g_signal_emit (self, signals[SIGNAL_POPULATE_POPUP], 0,
Matthias Clasen's avatar
Matthias Clasen committed
226
                     menu, info);
227 228 229 230 231 232 233 234

      g_object_unref (info);

      /* see if clients added menu items to this container */
      children = gtk_container_get_children (GTK_CONTAINER (menu));
      n_children = g_list_length (children);

      if (n_children > 0)
Matthias Clasen's avatar
Matthias Clasen committed
235 236 237 238 239 240
        {
          /* actually popup the menu */
          gtk_menu_attach_to_widget (GTK_MENU (menu), self->priv->program_list, NULL);
          gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
                          event->button, event->time);
        }
241 242 243 244 245 246 247

      g_list_free (children);
    }

  return FALSE;
}

248 249
static gboolean
path_is_heading (GtkTreeView *view,
Matthias Clasen's avatar
Matthias Clasen committed
250
                 GtkTreePath *path)
251 252 253 254 255 256 257 258
{
  GtkTreeIter iter;
  GtkTreeModel *model;
  gboolean res;

  model = gtk_tree_view_get_model (view);
  gtk_tree_model_get_iter (model, &iter, path);
  gtk_tree_model_get (model, &iter,
Matthias Clasen's avatar
Matthias Clasen committed
259 260
                      COLUMN_HEADING, &res,
                      -1);
261 262 263 264

  return res;
}

265
static void
Matthias Clasen's avatar
Matthias Clasen committed
266 267 268 269
program_list_selection_activated (GtkTreeView       *view,
                                  GtkTreePath       *path,
                                  GtkTreeViewColumn *column,
                                  gpointer           user_data)
270
{
271
  GtkAppChooserWidget *self = user_data;
272 273
  GtkTreeSelection *selection;

274 275 276
  if (path_is_heading (view, path))
    return;

277 278 279 280 281
  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self->priv->program_list));

  refresh_and_emit_app_selected (self, selection);

  g_signal_emit (self, signals[SIGNAL_APPLICATION_ACTIVATED], 0,
Matthias Clasen's avatar
Matthias Clasen committed
282
                 self->priv->selected_app_info);
283 284 285
}

static gboolean
286
gtk_app_chooser_search_equal_func (GtkTreeModel *model,
Matthias Clasen's avatar
Matthias Clasen committed
287 288 289 290
                                   gint          column,
                                   const gchar  *key,
                                   GtkTreeIter  *iter,
                                   gpointer      user_data)
291
{
Matthias Clasen's avatar
Matthias Clasen committed
292 293 294 295
  gchar *normalized_key;
  gchar *name, *normalized_name;
  gchar *path, *normalized_path;
  gchar *basename, *normalized_basename;
296 297 298 299 300 301 302 303 304 305
  gboolean ret;

  if (key != NULL)
    {
      normalized_key = g_utf8_casefold (key, -1);
      g_assert (normalized_key != NULL);

      ret = TRUE;

      gtk_tree_model_get (model, iter,
Matthias Clasen's avatar
Matthias Clasen committed
306 307 308
                          COLUMN_NAME, &name,
                          COLUMN_EXEC, &path,
                          -1);
309 310

      if (name != NULL)
Matthias Clasen's avatar
Matthias Clasen committed
311 312 313
        {
          normalized_name = g_utf8_casefold (name, -1);
          g_assert (normalized_name != NULL);
314

Matthias Clasen's avatar
Matthias Clasen committed
315 316
          if (strncmp (normalized_name, normalized_key, strlen (normalized_key)) == 0)
            ret = FALSE;
317

Matthias Clasen's avatar
Matthias Clasen committed
318 319
          g_free (normalized_name);
        }
320 321

      if (ret && path != NULL)
Matthias Clasen's avatar
Matthias Clasen committed
322 323 324
        {
          normalized_path = g_utf8_casefold (path, -1);
          g_assert (normalized_path != NULL);
325

Matthias Clasen's avatar
Matthias Clasen committed
326 327
          basename = g_path_get_basename (path);
          g_assert (basename != NULL);
328

Matthias Clasen's avatar
Matthias Clasen committed
329 330
          normalized_basename = g_utf8_casefold (basename, -1);
          g_assert (normalized_basename != NULL);
331

Matthias Clasen's avatar
Matthias Clasen committed
332 333 334
          if (strncmp (normalized_path, normalized_key, strlen (normalized_key)) == 0 ||
              strncmp (normalized_basename, normalized_key, strlen (normalized_key)) == 0)
            ret = FALSE;
335

Matthias Clasen's avatar
Matthias Clasen committed
336 337 338 339
          g_free (basename);
          g_free (normalized_basename);
          g_free (normalized_path);
        }
340 341 342 343 344 345 346 347 348 349 350 351 352 353

      g_free (name);
      g_free (path);
      g_free (normalized_key);

      return ret;
    }
  else
    {
      return TRUE;
    }
}

static gint
354
gtk_app_chooser_sort_func (GtkTreeModel *model,
Matthias Clasen's avatar
Matthias Clasen committed
355 356 357
                           GtkTreeIter  *a,
                           GtkTreeIter  *b,
                           gpointer      user_data)
358 359
{
  gboolean a_recommended, b_recommended;
360
  gboolean a_fallback, b_fallback;
361
  gboolean a_heading, b_heading;
362
  gboolean a_default, b_default;
363
  gchar *a_name, *b_name, *a_casefold, *b_casefold;
364
  gint retval = 0;
365 366 367 368 369 370 371 372

  /* this returns:
   * - <0 if a should show before b
   * - =0 if a is the same as b
   * - >0 if a should show after b
   */

  gtk_tree_model_get (model, a,
Matthias Clasen's avatar
Matthias Clasen committed
373 374 375 376 377 378
                      COLUMN_NAME, &a_name,
                      COLUMN_RECOMMENDED, &a_recommended,
                      COLUMN_FALLBACK, &a_fallback,
                      COLUMN_HEADING, &a_heading,
                      COLUMN_DEFAULT, &a_default,
                      -1);
379 380

  gtk_tree_model_get (model, b,
Matthias Clasen's avatar
Matthias Clasen committed
381 382 383 384 385 386
                      COLUMN_NAME, &b_name,
                      COLUMN_RECOMMENDED, &b_recommended,
                      COLUMN_FALLBACK, &b_fallback,
                      COLUMN_HEADING, &b_heading,
                      COLUMN_DEFAULT, &b_default,
                      -1);
387

388 389 390 391 392 393 394 395 396 397 398 399 400
  /* the default one always wins */
  if (a_default && !b_default)
    {
      retval = -1;
      goto out;
    }

  if (b_default && !a_default)
    {
      retval = 1;
      goto out;
    }
  
401 402 403 404 405 406 407 408 409 410 411 412 413
  /* the recommended one always wins */
  if (a_recommended && !b_recommended)
    {
      retval = -1;
      goto out;
    }

  if (b_recommended && !a_recommended)
    {
      retval = 1;
      goto out;
    }

414 415 416 417 418 419 420 421 422 423 424 425 426 427
  /* the recommended one always wins */
  if (a_fallback && !b_fallback)
    {
      retval = -1;
      goto out;
    }

  if (b_fallback && !a_fallback)
    {
      retval = 1;
      goto out;
    }

  /* they're both recommended/falback or not, so if one is a heading, wins */
428 429
  if (a_heading)
    {
430
      retval = -1;
431 432 433 434 435
      goto out;
    }

  if (b_heading)
    {
436
      retval = 1;
437 438 439
      goto out;
    }

440 441 442 443
  /* don't order by name recommended applications, but use GLib's ordering */
  if (!a_recommended)
    {
      a_casefold = a_name != NULL ?
Matthias Clasen's avatar
Matthias Clasen committed
444
        g_utf8_casefold (a_name, -1) : NULL;
445
      b_casefold = b_name != NULL ?
Matthias Clasen's avatar
Matthias Clasen committed
446
        g_utf8_casefold (b_name, -1) : NULL;
447

448
      retval = g_strcmp0 (a_casefold, b_casefold);
449

450 451 452
      g_free (a_casefold);
      g_free (b_casefold);
    }
453 454 455 456 457 458 459 460 461 462

 out:
  g_free (a_name);
  g_free (b_name);

  return retval;
}

static void
padding_cell_renderer_func (GtkTreeViewColumn *column,
Matthias Clasen's avatar
Matthias Clasen committed
463 464 465 466
                            GtkCellRenderer   *cell,
                            GtkTreeModel      *model,
                            GtkTreeIter       *iter,
                            gpointer           user_data)
467 468 469 470
{
  gboolean heading;

  gtk_tree_model_get (model, iter,
Matthias Clasen's avatar
Matthias Clasen committed
471 472
                      COLUMN_HEADING, &heading,
                      -1);
473 474
  if (heading)
    g_object_set (cell,
Matthias Clasen's avatar
Matthias Clasen committed
475 476 477 478
                  "visible", FALSE,
                  "xpad", 0,
                  "ypad", 0,
                  NULL);
479 480
  else
    g_object_set (cell,
Matthias Clasen's avatar
Matthias Clasen committed
481 482 483 484
                  "visible", TRUE,
                  "xpad", 3,
                  "ypad", 3,
                  NULL);
485 486 487
}

static gboolean
488
gtk_app_chooser_selection_func (GtkTreeSelection *selection,
Matthias Clasen's avatar
Matthias Clasen committed
489 490 491 492
                                GtkTreeModel     *model,
                                GtkTreePath      *path,
                                gboolean          path_currently_selected,
                                gpointer          user_data)
493 494 495 496 497 498
{
  GtkTreeIter iter;
  gboolean heading;

  gtk_tree_model_get_iter (model, &iter, path);
  gtk_tree_model_get (model, &iter,
Matthias Clasen's avatar
Matthias Clasen committed
499 500
                      COLUMN_HEADING, &heading,
                      -1);
501 502 503 504 505 506

  return !heading;
}

static gint
compare_apps_func (gconstpointer a,
Matthias Clasen's avatar
Matthias Clasen committed
507
                   gconstpointer b)
508 509 510 511
{
  return !g_app_info_equal (G_APP_INFO (a), G_APP_INFO (b));
}

512
static gboolean
513
gtk_app_chooser_widget_add_section (GtkAppChooserWidget *self,
Matthias Clasen's avatar
Matthias Clasen committed
514 515 516 517 518 519
                                    const gchar         *heading_title,
                                    gboolean             show_headings,
                                    gboolean             recommended,
                                    gboolean             fallback,
                                    GList               *applications,
                                    GList               *exclude_apps)
520
{
521
  gboolean heading_added, unref_icon;
522 523 524 525 526
  GtkTreeIter iter;
  GAppInfo *app;
  gchar *app_string, *bold_string;
  GIcon *icon;
  GList *l;
527
  gboolean retval;
528

529
  retval = FALSE;
530
  heading_added = FALSE;
531
  bold_string = g_strdup_printf ("<b>%s</b>", heading_title);
532
  
533
  for (l = applications; l != NULL; l = l->next)
534
    {
535
      app = l->data;
536

537
      if (!g_app_info_supports_uris (app) &&
Matthias Clasen's avatar
Matthias Clasen committed
538 539
          !g_app_info_supports_files (app))
        continue;
540

Matthias Clasen's avatar
Matthias Clasen committed
541 542 543
      if (g_list_find_custom (exclude_apps, app,
                              (GCompareFunc) compare_apps_func))
        continue;
544

545
      if (!heading_added && show_headings)
Matthias Clasen's avatar
Matthias Clasen committed
546 547 548 549 550 551 552 553 554 555 556
        {
          gtk_list_store_append (self->priv->program_list_store, &iter);
          gtk_list_store_set (self->priv->program_list_store, &iter,
                              COLUMN_HEADING_TEXT, bold_string,
                              COLUMN_HEADING, TRUE,
                              COLUMN_RECOMMENDED, recommended,
                              COLUMN_FALLBACK, fallback,
                              -1);

          heading_added = TRUE;
        }
557

558 559 560
      app_string = g_markup_printf_escaped ("%s",
                                            g_app_info_get_name (app) != NULL ?
                                            g_app_info_get_name (app) : "");
561 562

      icon = g_app_info_get_icon (app);
563
      unref_icon = FALSE;
564
      if (icon == NULL)
Matthias Clasen's avatar
Matthias Clasen committed
565 566 567 568
        {
          icon = g_themed_icon_new ("application-x-executable");
          unref_icon = TRUE;
        }
569

570 571
      gtk_list_store_append (self->priv->program_list_store, &iter);
      gtk_list_store_set (self->priv->program_list_store, &iter,
Matthias Clasen's avatar
Matthias Clasen committed
572 573
                          COLUMN_APP_INFO, app,
                          COLUMN_GICON, icon,
574
                          COLUMN_NAME, g_app_info_get_name (app),
Matthias Clasen's avatar
Matthias Clasen committed
575 576 577 578 579 580
                          COLUMN_DESC, app_string,
                          COLUMN_EXEC, g_app_info_get_executable (app),
                          COLUMN_HEADING, FALSE,
                          COLUMN_RECOMMENDED, recommended,
                          COLUMN_FALLBACK, fallback,
                          -1);
581

582 583
      retval = TRUE;

584 585
      g_free (app_string);
      if (unref_icon)
Matthias Clasen's avatar
Matthias Clasen committed
586
        g_object_unref (icon);
587 588
    }

589
  g_free (bold_string);
590 591

  return retval;
592
}
593

594 595

static void
596
gtk_app_chooser_add_default (GtkAppChooserWidget *self,
Matthias Clasen's avatar
Matthias Clasen committed
597
                             GAppInfo            *app)
598 599 600 601 602 603 604 605 606 607 608
{
  GtkTreeIter iter;
  GIcon *icon;
  gchar *string;
  gboolean unref_icon;

  unref_icon = FALSE;
  string = g_strdup_printf ("<b>%s</b>", _("Default Application"));

  gtk_list_store_append (self->priv->program_list_store, &iter);
  gtk_list_store_set (self->priv->program_list_store, &iter,
Matthias Clasen's avatar
Matthias Clasen committed
609 610 611 612
                      COLUMN_HEADING_TEXT, string,
                      COLUMN_HEADING, TRUE,
                      COLUMN_DEFAULT, TRUE,
                      -1);
613 614 615

  g_free (string);

616 617 618
  string = g_markup_printf_escaped ("%s",
                                    g_app_info_get_name (app) != NULL ?
                                    g_app_info_get_name (app) : "");
619 620 621 622 623 624 625 626 627 628

  icon = g_app_info_get_icon (app);
  if (icon == NULL)
    {
      icon = g_themed_icon_new ("application-x-executable");
      unref_icon = TRUE;
    }

  gtk_list_store_append (self->priv->program_list_store, &iter);
  gtk_list_store_set (self->priv->program_list_store, &iter,
Matthias Clasen's avatar
Matthias Clasen committed
629 630
                      COLUMN_APP_INFO, app,
                      COLUMN_GICON, icon,
631
                      COLUMN_NAME, g_app_info_get_name (app),
Matthias Clasen's avatar
Matthias Clasen committed
632 633 634 635 636
                      COLUMN_DESC, string,
                      COLUMN_EXEC, g_app_info_get_executable (app),
                      COLUMN_HEADING, FALSE,
                      COLUMN_DEFAULT, TRUE,
                      -1);
637 638 639 640 641 642 643

  g_free (string);

  if (unref_icon)
    g_object_unref (icon);
}

644
static void
645
add_no_applications_label (GtkAppChooserWidget *self)
646
{
647
  gchar *text = NULL, *desc = NULL;
648
  const gchar *string;
649
  GtkTreeIter iter;
650

651 652
  if (self->priv->default_text == NULL)
    {
653 654 655
      if (self->priv->content_type)
	desc = g_content_type_get_description (self->priv->content_type);

656
      string = text = g_strdup_printf (_("No applications available to open \"%s\""),
Matthias Clasen's avatar
Matthias Clasen committed
657
                                       desc);
658 659 660 661 662 663
      g_free (desc);
    }
  else
    {
      string = self->priv->default_text;
    }
664

665 666
  gtk_list_store_append (self->priv->program_list_store, &iter);
  gtk_list_store_set (self->priv->program_list_store, &iter,
Matthias Clasen's avatar
Matthias Clasen committed
667 668 669
                      COLUMN_HEADING_TEXT, string,
                      COLUMN_HEADING, TRUE,
                      -1);
670

Matthias Clasen's avatar
Matthias Clasen committed
671
  g_free (text);
672
}
673

674
static void
675
gtk_app_chooser_widget_select_first (GtkAppChooserWidget *self)
676 677 678 679 680 681 682 683 684 685 686
{
  GtkTreeIter iter;
  GAppInfo *info = NULL;
  GtkTreeModel *model;

  model = gtk_tree_view_get_model (GTK_TREE_VIEW (self->priv->program_list));
  gtk_tree_model_get_iter_first (model, &iter);

  while (info == NULL)
    {
      gtk_tree_model_get (model, &iter,
Matthias Clasen's avatar
Matthias Clasen committed
687 688
                          COLUMN_APP_INFO, &info,
                          -1);
689 690

      if (info != NULL)
Matthias Clasen's avatar
Matthias Clasen committed
691
        break;
692 693

      if (!gtk_tree_model_iter_next (model, &iter))
Matthias Clasen's avatar
Matthias Clasen committed
694
        break;
695 696 697 698 699 700 701 702 703 704 705 706 707
    }

  if (info != NULL)
    {
      GtkTreeSelection *selection;

      selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self->priv->program_list));
      gtk_tree_selection_select_iter (selection, &iter);

      g_object_unref (info);
    }
}

708
static void
709
gtk_app_chooser_widget_real_add_items (GtkAppChooserWidget *self)
710
{
Matthias Clasen's avatar
Matthias Clasen committed
711 712 713
  GList *all_applications = NULL;
  GList *recommended_apps = NULL;
  GList *fallback_apps = NULL;
714 715
  GList *exclude_apps = NULL;
  GAppInfo *default_app = NULL;
716 717
  gboolean show_headings;
  gboolean apps_added;
718

719 720 721 722 723 724
  show_headings = TRUE;
  apps_added = FALSE;

  if (self->priv->show_all)
    show_headings = FALSE;

725
  if (self->priv->show_default && self->priv->content_type)
726 727 728 729
    {
      default_app = g_app_info_get_default_for_type (self->priv->content_type, FALSE);

      if (default_app != NULL)
Matthias Clasen's avatar
Matthias Clasen committed
730 731 732 733 734
        {
          gtk_app_chooser_add_default (self, default_app);
          apps_added = TRUE;
          exclude_apps = g_list_prepend (exclude_apps, default_app);
        }
735 736
    }

Fridrich Strba's avatar
Fridrich Strba committed
737
#ifndef G_OS_WIN32
738
  if ((self->priv->content_type && self->priv->show_recommended) || self->priv->show_all)
739
    {
740 741
      if (self->priv->content_type)
	recommended_apps = g_app_info_get_recommended_for_type (self->priv->content_type);
742

743
      apps_added |= gtk_app_chooser_widget_add_section (self, _("Recommended Applications"),
Matthias Clasen's avatar
Matthias Clasen committed
744 745 746 747
                                                        show_headings,
                                                        !self->priv->show_all, /* mark as recommended */
                                                        FALSE, /* mark as fallback */
                                                        recommended_apps, exclude_apps);
748 749

      exclude_apps = g_list_concat (exclude_apps,
Matthias Clasen's avatar
Matthias Clasen committed
750
                                    g_list_copy (recommended_apps));
751
    }
752

753
  if ((self->priv->content_type && self->priv->show_fallback) || self->priv->show_all)
754
    {
755 756
      if (self->priv->content_type)
	fallback_apps = g_app_info_get_fallback_for_type (self->priv->content_type);
757

758
      apps_added |= gtk_app_chooser_widget_add_section (self, _("Related Applications"),
Matthias Clasen's avatar
Matthias Clasen committed
759 760 761 762
                                                        show_headings,
                                                        FALSE, /* mark as recommended */
                                                        !self->priv->show_all, /* mark as fallback */
                                                        fallback_apps, exclude_apps);
763
      exclude_apps = g_list_concat (exclude_apps,
Matthias Clasen's avatar
Matthias Clasen committed
764
                                    g_list_copy (fallback_apps));
765
    }
Fridrich Strba's avatar
Fridrich Strba committed
766
#endif
767

768
  if (self->priv->show_other || self->priv->show_all)
769
    {
770 771
      all_applications = g_app_info_get_all ();

772
      apps_added |= gtk_app_chooser_widget_add_section (self, _("Other Applications"),
Matthias Clasen's avatar
Matthias Clasen committed
773 774 775 776
                                                        show_headings,
                                                        FALSE,
                                                        FALSE,
                                                        all_applications, exclude_apps);
777
    }
778

779 780
  if (!apps_added)
    add_no_applications_label (self);
781

782
  gtk_app_chooser_widget_select_first (self);
783 784 785 786

  if (default_app != NULL)
    g_object_unref (default_app);

787 788
  if (all_applications != NULL)
    g_list_free_full (all_applications, g_object_unref);
789

790 791
  if (recommended_apps != NULL)
    g_list_free_full (recommended_apps, g_object_unref);
792

793 794
  if (fallback_apps != NULL)
    g_list_free_full (fallback_apps, g_object_unref);
795

796 797
  if (exclude_apps != NULL)
    g_list_free (exclude_apps);
798 799 800
}

static void
801
gtk_app_chooser_widget_add_items (GtkAppChooserWidget *self)
802 803 804 805 806 807 808
{
  GtkCellRenderer *renderer;
  GtkTreeViewColumn *column;
  GtkTreeModel *sort;

  /* create list store */
  self->priv->program_list_store = gtk_list_store_new (NUM_COLUMNS,
Matthias Clasen's avatar
Matthias Clasen committed
809 810 811 812 813 814 815 816 817 818
                                                       G_TYPE_APP_INFO,
                                                       G_TYPE_ICON,
                                                       G_TYPE_STRING,
                                                       G_TYPE_STRING,
                                                       G_TYPE_STRING,
                                                       G_TYPE_BOOLEAN,
                                                       G_TYPE_BOOLEAN,
                                                       G_TYPE_STRING,
                                                       G_TYPE_BOOLEAN,
                                                       G_TYPE_BOOLEAN);
819 820
  sort = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (self->priv->program_list_store));

Matthias Clasen's avatar
Matthias Clasen committed
821 822
  gtk_tree_view_set_model (GTK_TREE_VIEW (self->priv->program_list),
                           GTK_TREE_MODEL (sort));
823
  gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (sort),
Matthias Clasen's avatar
Matthias Clasen committed
824 825
                                        COLUMN_NAME,
                                        GTK_SORT_ASCENDING);
826
  gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sort),
Matthias Clasen's avatar
Matthias Clasen committed
827 828 829
                                   COLUMN_NAME,
                                   gtk_app_chooser_sort_func,
                                   self, NULL);
830
  gtk_tree_view_set_search_column (GTK_TREE_VIEW (self->priv->program_list),
Matthias Clasen's avatar
Matthias Clasen committed
831
                                   COLUMN_NAME);
832
  gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (self->priv->program_list),
Matthias Clasen's avatar
Matthias Clasen committed
833 834
                                       gtk_app_chooser_search_equal_func,
                                       NULL, NULL);
835 836 837 838 839 840 841

  column = gtk_tree_view_column_new ();

  /* initial padding */
  renderer = gtk_cell_renderer_text_new ();
  gtk_tree_view_column_pack_start (column, renderer, FALSE);
  g_object_set (renderer,
Matthias Clasen's avatar
Matthias Clasen committed
842 843
                "xpad", self->priv->show_all ? 0 : 6,
                NULL);
844 845 846 847 848 849
  self->priv->padding_renderer = renderer;

  /* heading text renderer */
  renderer = gtk_cell_renderer_text_new ();
  gtk_tree_view_column_pack_start (column, renderer, FALSE);
  gtk_tree_view_column_set_attributes (column, renderer,
Matthias Clasen's avatar
Matthias Clasen committed
850 851 852
                                       "markup", COLUMN_HEADING_TEXT,
                                       "visible", COLUMN_HEADING,
                                       NULL);
853
  g_object_set (renderer,
Matthias Clasen's avatar
Matthias Clasen committed
854 855 856 857 858
                "ypad", 6,
                "xpad", 0,
                "wrap-width", 350,
                "wrap-mode", PANGO_WRAP_WORD,
                NULL);
859 860 861 862 863

  /* padding renderer for non-heading cells */
  renderer = gtk_cell_renderer_text_new ();
  gtk_tree_view_column_pack_start (column, renderer, FALSE);
  gtk_tree_view_column_set_cell_data_func (column, renderer,
Matthias Clasen's avatar
Matthias Clasen committed
864 865
                                           padding_cell_renderer_func,
                                           NULL, NULL);
866 867 868 869 870

  /* app icon renderer */
  renderer = gtk_cell_renderer_pixbuf_new ();
  gtk_tree_view_column_pack_start (column, renderer, FALSE);
  gtk_tree_view_column_set_attributes (column, renderer,
Matthias Clasen's avatar
Matthias Clasen committed
871 872
                                       "gicon", COLUMN_GICON,
                                       NULL);
873
  g_object_set (renderer,
874
                "stock-size", GTK_ICON_SIZE_MENU,
Matthias Clasen's avatar
Matthias Clasen committed
875
                NULL);
876 877 878 879 880

  /* app name renderer */
  renderer = gtk_cell_renderer_text_new ();
  gtk_tree_view_column_pack_start (column, renderer, TRUE);
  gtk_tree_view_column_set_attributes (column, renderer,
Matthias Clasen's avatar
Matthias Clasen committed
881 882
                                       "markup", COLUMN_DESC,
                                       NULL);
883
  g_object_set (renderer,
Matthias Clasen's avatar
Matthias Clasen committed
884 885 886
                "ellipsize", PANGO_ELLIPSIZE_END,
                "ellipsize-set", TRUE,
                NULL);
887
  
888 889
  gtk_tree_view_column_set_sort_column_id (column, COLUMN_NAME);
  gtk_tree_view_append_column (GTK_TREE_VIEW (self->priv->program_list), column);
890 891

  /* populate the widget */
892
  gtk_app_chooser_widget_real_add_items (self);
893 894 895
}

static void
Matthias Clasen's avatar
Matthias Clasen committed
896 897 898 899
gtk_app_chooser_widget_set_property (GObject      *object,
                                     guint         property_id,
                                     const GValue *value,
                                     GParamSpec   *pspec)
900
{
901
  GtkAppChooserWidget *self = GTK_APP_CHOOSER_WIDGET (object);
902 903 904 905 906 907

  switch (property_id)
    {
    case PROP_CONTENT_TYPE:
      self->priv->content_type = g_value_dup_string (value);
      break;
908
    case PROP_SHOW_DEFAULT:
909
      gtk_app_chooser_widget_set_show_default (self, g_value_get_boolean (value));
910
      break;
911
    case PROP_SHOW_RECOMMENDED:
912
      gtk_app_chooser_widget_set_show_recommended (self, g_value_get_boolean (value));
913 914
      break;
    case PROP_SHOW_FALLBACK:
915
      gtk_app_chooser_widget_set_show_fallback (self, g_value_get_boolean (value));
916 917
      break;
    case PROP_SHOW_OTHER:
918
      gtk_app_chooser_widget_set_show_other (self, g_value_get_boolean (value));
919 920
      break;
    case PROP_SHOW_ALL:
921
      gtk_app_chooser_widget_set_show_all (self, g_value_get_boolean (value));
922
      break;
923
    case PROP_DEFAULT_TEXT:
924
      gtk_app_chooser_widget_set_default_text (self, g_value_get_string (value));
925
      break;
926 927 928 929 930 931 932
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
Matthias Clasen's avatar
Matthias Clasen committed
933 934 935 936
gtk_app_chooser_widget_get_property (GObject    *object,
                                     guint       property_id,
                                     GValue     *value,
                                     GParamSpec *pspec)
937
{
938
  GtkAppChooserWidget *self = GTK_APP_CHOOSER_WIDGET (object);
939 940 941 942 943 944

  switch (property_id)
    {
    case PROP_CONTENT_TYPE:
      g_value_set_string (value, self->priv->content_type);
      break;
945 946 947
    case PROP_SHOW_DEFAULT:
      g_value_set_boolean (value, self->priv->show_default);
      break;
948 949 950 951 952 953 954 955 956 957 958 959
    case PROP_SHOW_RECOMMENDED:
      g_value_set_boolean (value, self->priv->show_recommended);
      break;
    case PROP_SHOW_FALLBACK:
      g_value_set_boolean (value, self->priv->show_fallback);
      break;
    case PROP_SHOW_OTHER:
      g_value_set_boolean (value, self->priv->show_other);
      break;
    case PROP_SHOW_ALL:
      g_value_set_boolean (value, self->priv->show_all);
      break;
960 961 962
    case PROP_DEFAULT_TEXT:
      g_value_set_string (value, self->priv->default_text);
      break;
963 964 965 966 967 968 969
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
970
gtk_app_chooser_widget_constructed (GObject *object)
971
{
972
  GtkAppChooserWidget *self = GTK_APP_CHOOSER_WIDGET (object);
973

974 975
  if (G_OBJECT_CLASS (gtk_app_chooser_widget_parent_class)->constructed != NULL)
    G_OBJECT_CLASS (gtk_app_chooser_widget_parent_class)->constructed (object);
976

977
  gtk_app_chooser_widget_add_items (self);
978 979 980
}

static void
981
gtk_app_chooser_widget_finalize (GObject *object)
982
{
983
  GtkAppChooserWidget *self = GTK_APP_CHOOSER_WIDGET (object);
984 985

  g_free (self->priv->content_type);
986
  g_free (self->priv->default_text);
987

988
  G_OBJECT_CLASS (gtk_app_chooser_widget_parent_class)->finalize (object);
989 990 991
}

static void
992
gtk_app_chooser_widget_dispose (GObject *object)
993
{
994
  GtkAppChooserWidget *self = GTK_APP_CHOOSER_WIDGET (object);
995

996
  g_clear_object (&self->priv->selected_app_info);
997

998
  G_OBJECT_CLASS (gtk_app_chooser_widget_parent_class)->dispose (object);
999 1000 1001
}

static void
1002
gtk_app_chooser_widget_class_init (GtkAppChooserWidgetClass *klass)
1003 1004 1005 1006 1007
{
  GObjectClass *gobject_class;
  GParamSpec *pspec;

  gobject_class = G_OBJECT_CLASS (klass);
1008 1009 1010 1011 1012
  gobject_class->dispose = gtk_app_chooser_widget_dispose;
  gobject_class->finalize = gtk_app_chooser_widget_finalize;
  gobject_class->set_property = gtk_app_chooser_widget_set_property;
  gobject_class->get_property = gtk_app_chooser_widget_get_property;
  gobject_class->constructed = gtk_app_chooser_widget_constructed;
1013 1014 1015

  g_object_class_override_property (gobject_class, PROP_CONTENT_TYPE, "content-type");

Matthias Clasen's avatar
Matthias Clasen committed
1016 1017 1018 1019 1020 1021 1022 1023
  /**
   * GtkAppChooserWidget:show-default:
   *
   * The ::show-default property determines whether the app chooser
   * should show the default handler for the content type in a
   * separate section. If %FALSE, the default handler is listed
   * among the recommended applications.
   */
1024
  pspec = g_param_spec_boolean ("show-default",
Matthias Clasen's avatar
Matthias Clasen committed
1025 1026 1027 1028
                                P_("Show default app"),
                                P_("Whether the widget should show the default application"),
                                FALSE,
                                G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
1029 1030
  g_object_class_install_property (gobject_class, PROP_SHOW_DEFAULT, pspec);

Matthias Clasen's avatar
Matthias Clasen committed
1031 1032 1033
  /**
   * GtkAppChooserWidget:show-recommended:
   *
1034 1035 1036 1037
   * The #GtkAppChooserWidget:show-recommended property determines
   * whether the app chooser should show a section for recommended
   * applications. If %FALSE, the recommended applications are listed
   * among the other applications.
Matthias Clasen's avatar
Matthias Clasen committed
1038
   */
1039
  pspec = g_param_spec_boolean ("show-recommended",
Matthias Clasen's avatar
Matthias Clasen committed
1040 1041 1042 1043
                                P_("Show recommended apps"),
                                P_("Whether the widget should show recommended applications"),
                                TRUE,
                                G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
1044 1045
  g_object_class_install_property (gobject_class, PROP_SHOW_RECOMMENDED, pspec);

Matthias Clasen's avatar
Matthias Clasen committed
1046 1047 1048
  /**
   * GtkAppChooserWidget:show-fallback:
   *
1049 1050 1051 1052
   * The #GtkAppChooserWidget:show-fallback property determines whether
   * the app chooser should show a section for fallback applications.
   * If %FALSE, the fallback applications are listed among the other
   * applications.
Matthias Clasen's avatar
Matthias Clasen committed
1053
   */
1054
  pspec = g_param_spec_boolean ("show-fallback",
Matthias Clasen's avatar
Matthias Clasen committed
1055 1056 1057 1058
                                P_("Show fallback apps"),
                                P_("Whether the widget should show fallback applications"),
                                FALSE,
                                G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
1059 1060
  g_object_class_install_property (gobject_class, PROP_SHOW_FALLBACK, pspec);

Matthias Clasen's avatar
Matthias Clasen committed
1061 1062 1063
  /**
   * GtkAppChooserWidget:show-other:
   *
1064 1065
   * The #GtkAppChooserWidget:show-other property determines whether
   * the app chooser should show a section for other applications.
Matthias Clasen's avatar
Matthias Clasen committed
1066
   */
1067
  pspec = g_param_spec_boolean ("show-other",
Matthias Clasen's avatar
Matthias Clasen committed
1068 1069 1070 1071
                                P_("Show other apps"),
                                P_("Whether the widget should show other applications"),
                                FALSE,
                                G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
1072 1073
  g_object_class_install_property (gobject_class, PROP_SHOW_OTHER, pspec);

Matthias Clasen's avatar
Matthias Clasen committed
1074 1075 1076
  /**
   * GtkAppChooserWidget:show-all:
   *
1077 1078 1079
   * If the #GtkAppChooserWidget:show-all property is %TRUE, the app
   * chooser presents all applications in a single list, without
   * subsections for default, recommended or related applications.
Matthias Clasen's avatar
Matthias Clasen committed
1080
   */
1081
  pspec = g_param_spec_boolean ("show-all",
Matthias Clasen's avatar
Matthias Clasen committed
1082 1083 1084 1085
                                P_("Show all apps"),
                                P_("Whether the widget should show all applications"),
                                FALSE,
                                G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
1086 1087
  g_object_class_install_property (gobject_class, PROP_SHOW_ALL, pspec);

1088 1089 1090
  /**
   * GtkAppChooserWidget:default-text:
   *
1091 1092 1093
   * The #GtkAppChooserWidget:default-text property determines the text
   * that appears in the widget when there are no applications for the
   * given content type.
1094 1095
   * See also gtk_app_chooser_widget_set_default_text().
   */
1096
  pspec = g_param_spec_string ("default-text",
Matthias Clasen's avatar
Matthias Clasen committed
1097 1098 1099 1100
                               P_("Widget's default text"),
                               P_("The default text appearing when there are no applications"),
                               NULL,
                               G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
1101 1102
  g_object_class_install_property (gobject_class, PROP_DEFAULT_TEXT, pspec);

Cosimo Cecchi's avatar
Cosimo Cecchi committed
1103 1104 1105 1106 1107 1108 1109
  /**
   * GtkAppChooserWidget::application-selected:
   * @self: the object which received the signal
   * @application: the selected #GAppInfo
   *
   * Emitted when an application item is selected from the widget's list.
   */
1110 1111
  signals[SIGNAL_APPLICATION_SELECTED] =
    g_signal_new ("application-selected",
Matthias Clasen's avatar
Matthias Clasen committed
1112 1113 1114 1115 1116 1117 1118
                  GTK_TYPE_APP_CHOOSER_WIDGET,
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GtkAppChooserWidgetClass, application_selected),
                  NULL, NULL,
                  _gtk_marshal_VOID__OBJECT,
                  G_TYPE_NONE,
                  1, G_TYPE_APP_INFO);
1119

Cosimo Cecchi's avatar
Cosimo Cecchi committed
1120 1121 1122 1123 1124 1125
  /**
   * GtkAppChooserWidget::application-activated:
   * @self: the object which received the signal
   * @application: the activated #GAppInfo
   *
   * Emitted when an application item is activated from the widget's list.
1126
   *
Cosimo Cecchi's avatar
Cosimo Cecchi committed
1127 1128 1129 1130
   * This usually happens when the user double clicks an item, or an item
   * is selected and the user presses one of the keys Space, Shift+Space,
   * Return or Enter.
   */
1131 1132
  signals[SIGNAL_APPLICATION_ACTIVATED] =
    g_signal_new ("application-activated",
Matthias Clasen's avatar
Matthias Clasen committed
1133 1134 1135 1136 1137 1138 1139
                  GTK_TYPE_APP_CHOOSER_WIDGET,
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GtkAppChooserWidgetClass, application_activated),
                  NULL, NULL,
                  _gtk_marshal_VOID__OBJECT,
                  G_TYPE_NONE,
                  1, G_TYPE_APP_INFO);
1140

Cosimo Cecchi's avatar
Cosimo Cecchi committed
1141 1142 1143 1144 1145 1146 1147 1148
  /**
   * GtkAppChooserWidget::populate-popup:
   * @self: the object which received the signal
   * @menu: the #GtkMenu to populate
   * @application: the current #GAppInfo
   *
   * Emitted when a context menu is about to popup over an application item.
   * Clients can insert menu items into the provided #GtkMenu object in the
1149 1150
   * callback of this signal; the context menu will be shown over the item
   * if at least one item has been added to the menu.
Cosimo Cecchi's avatar
Cosimo Cecchi committed
1151
   */
1152 1153
  signals[SIGNAL_POPULATE_POPUP] =
    g_signal_new ("populate-popup",
Matthias Clasen's avatar
Matthias Clasen committed
1154 1155 1156 1157 1158 1159 1160
                  GTK_TYPE_APP_CHOOSER_WIDGET,
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GtkAppChooserWidgetClass, populate_popup),
                  NULL, NULL,
                  _gtk_marshal_VOID__OBJECT_OBJECT,
                  G_TYPE_NONE,
                  2, GTK_TYPE_MENU, G_TYPE_APP_INFO);
1161

1162
  g_type_class_add_private (klass, sizeof (GtkAppChooserWidgetPrivate));
1163 1164 1165
}

static void
1166
gtk_app_chooser_widget_init (GtkAppChooserWidget *self)
1167
{
1168
  GtkWidget *scrolled_window;
1169 1170
  GtkTreeSelection *selection;

1171
  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GTK_TYPE_APP_CHOOSER_WIDGET,
Matthias Clasen's avatar
Matthias Clasen committed
1172
                                            GtkAppChooserWidgetPrivate);
1173
  gtk_orientable_set_orientation (GTK_ORIENTABLE (self), GTK_ORIENTATION_VERTICAL);
1174 1175 1176 1177

  scrolled_window = gtk_scrolled_window_new (NULL, NULL);
  gtk_widget_set_size_request (scrolled_window, 400, 300);
  gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window),
Matthias Clasen's avatar
Matthias Clasen committed
1178
                                       GTK_SHADOW_IN);
1179
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
Matthias Clasen's avatar
Matthias Clasen committed
1180 1181
                                  GTK_POLICY_NEVER,
                                  GTK_POLICY_AUTOMATIC);
1182
  gtk_widget_show (scrolled_window);
1183 1184 1185

  self->priv->program_list = gtk_tree_view_new ();
  gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (self->priv->program_list),
Matthias Clasen's avatar
Matthias Clasen committed
1186
                                     FALSE);
1187 1188
  gtk_container_add (GTK_CONTAINER (scrolled_window), self->priv->program_list);
  gtk_box_pack_start (GTK_BOX (self), scrolled_window, TRUE, TRUE, 0);
1189
  gtk_widget_show (self->priv->program_list);
1190 1191

  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self->priv->program_list));
1192
  gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);
1193
  gtk_tree_selection_set_select_function (selection, gtk_app_chooser_selection_func,
Matthias Clasen's avatar
Matthias Clasen committed
1194
                                          self, NULL);
1195
  g_signal_connect_swapped (selection, "changed",
Matthias Clasen's avatar
Matthias Clasen committed
1196 1197
                            G_CALLBACK (refresh_and_emit_app_selected),
                            self);
1198
  g_signal_connect (self->priv->program_list, "row-activated",
Matthias Clasen's avatar
Matthias Clasen committed
1199 1200
                    G_CALLBACK (program_list_selection_activated),
                    self);
1201
  g_signal_connect (self->priv->program_list, "button-press-event",
Matthias Clasen's avatar
Matthias Clasen committed
1202 1203
                    G_CALLBACK (widget_button_press_event_cb),
                    self);
1204 1205 1206
}

static GAppInfo *
1207
gtk_app_chooser_widget_get_app_info (GtkAppChooser *object)
1208
{
1209
  GtkAppChooserWidget *self = GTK_APP_CHOOSER_WIDGET (object);
1210 1211 1212 1213 1214 1215 1216 1217

  if (self->priv->selected_app_info == NULL)
    return NULL;

  return g_object_ref (self->priv->selected_app_info);
}

static void
1218
gtk_app_chooser_widget_refresh (GtkAppChooser *object)
1219
{
1220
  GtkAppChooserWidget *self = GTK_APP_CHOOSER_WIDGET (object);
1221

1222 1223 1224 1225 1226 1227
  if (self->priv->program_list_store != NULL)
    {
      gtk_list_store_clear (self->priv->program_list_store);

      /* don't add additional xpad if we don't have headings */
      g_object_set (self->priv->padding_renderer,
Matthias Clasen's avatar
Matthias Clasen committed
1228 1229
                    "visible", !self->priv->show_all,
                    NULL);
1230

1231
      gtk_app_chooser_widget_real_add_items (self);
1232 1233 1234
    }
}

1235
static void
1236
gtk_app_chooser_widget_iface_init (GtkAppChooserIface *iface)
1237
{
1238 1239
  iface->get_app_info = gtk_app_chooser_widget_get_app_info;
  iface->refresh = gtk_app_chooser_widget_refresh;
1240 1241
}

Matthias Clasen's avatar
Matthias Clasen committed
1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252
/**
 * gtk_app_chooser_widget_new:
 * @content_type: the content type to show applications for
 *
 * Creates a new #GtkAppChooserWidget for applications
 * that can handle content of the given type.
 *
 * Returns: a newly created #GtkAppChooserWidget
 *
 * Since: 3.0
 */
1253
GtkWidget *
1254
gtk_app_chooser_widget_new (const gchar *content_type)
1255
{
1256
  return g_object_new (GTK_TYPE_APP_CHOOSER_WIDGET,
Matthias Clasen's avatar
Matthias Clasen committed
1257 1258
                       "content-type", content_type,
                       NULL);
1259 1260
}

Matthias Clasen's avatar
Matthias Clasen committed
1261 1262 1263 1264 1265 1266 1267 1268 1269 1270
/**
 * gtk_app_chooser_widget_set_show_default:
 * @self: a #GtkAppChooserWidget
 * @setting: the new value for #GtkAppChooserWidget:show-default
 *
 * Sets whether the app chooser should show the default handler
 * for the content type in a separate section.
 *
 * Since: 3.0
 */
1271
void
1272
gtk_app_chooser_widget_set_show_default (GtkAppChooserWidget *self,
Matthias Clasen's avatar
Matthias Clasen committed
1273
                                         gboolean             setting)
1274
{
1275
  g_return_if_fail (GTK_IS_APP_CHOOSER_WIDGET (self));
1276 1277 1278 1279 1280 1281 1282

  if (self->priv->show_default != setting)
    {
      self->priv->show_default = setting;

      g_object_notify (G_OBJECT (self), "show-default");

1283
      gtk_app_chooser_refresh (GTK_APP_CHOOSER (self));
1284 1285 1286
    }
}

Matthias Clasen's avatar
Matthias Clasen committed
1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297
/**
 * gtk_app_chooser_widget_get_show_default:
 * @self: a #GtkAppChooserWidget
 *
 * Returns the current value of the #GtkAppChooserWidget:show-default
 * property.
 *
 * Returns: the value of #GtkAppChooserWidget:show-default
 *
 * Since: 3.0
 */
1298
gboolean
1299
gtk_app_chooser_widget_get_show_default (GtkAppChooserWidget *self)
1300
{
1301
  g_return_val_if_fail (GTK_IS_APP_CHOOSER_WIDGET (self), FALSE);
1302 1303 1304 1305

  return self->priv->show_default;
}

Matthias Clasen's avatar
Matthias Clasen committed
1306 1307 1308 1309 1310 1311 1312 1313 1314 1315
/**
 * gtk_app_chooser_widget_set_show_recommended:
 * @self: a #GtkAppChooserWidget
 * @setting: the new value for #GtkAppChooserWidget:show-recommended
 *
 * Sets whether the app chooser should show recommended applications
 * for the content type in a separate section.
 *
 * Since: 3.0
 */
1316
void
1317
gtk_app_chooser_widget_set_show_recommended (GtkAppChooserWidget *self,
Matthias Clasen's avatar
Matthias Clasen committed
1318
                                             gboolean             setting)
1319
{
1320
  g_return_if_fail (GTK_IS_APP_CHOOSER_WIDGET (self));
1321

1322
  if (self->priv->show_recommended != setting)
1323
    {
1324 1325 1326
      self->priv->show_recommended = setting;

      g_object_notify (G_OBJECT (self), "show-recommended");
1327

1328
      gtk_app_chooser_refresh (GTK_APP_CHOOSER (self));
1329 1330 1331
    }
}

Matthias Clasen's avatar
Matthias Clasen committed
1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342
/**
 * gtk_app_chooser_widget_get_show_recommended:
 * @self: a #GtkAppChooserWidget
 *
 * Returns the current value of the #GtkAppChooserWidget:show-recommended
 * property.
 *
 * Returns: the value of #GtkAppChooserWidget:show-recommended
 *
 * Since: 3.0
 */
1343
gboolean
1344
gtk_app_chooser_widget_get_show_recommended (GtkAppChooserWidget *self)
1345
{
1346
  g_return_val_if_fail (GTK_IS_APP_CHOOSER_WIDGET (self), FALSE);
1347 1348 1349 1350

  return self->priv->show_recommended;
}

Matthias Clasen's avatar
Matthias Clasen committed
1351 1352 1353 1354 1355 1356 1357 1358 1359 1360
/**
 * gtk_app_chooser_widget_set_show_fallback:
 * @self: a #GtkAppChooserWidget
 * @setting: the new value for #GtkAppChooserWidget:show-fallback
 *
 * Sets whether the app chooser should show related applications
 * for the content type in a separate section.
 *
 * Since: 3.0
 */
1361
void
1362
gtk_app_chooser_widget_set_show_fallback (GtkAppChooserWidget *self,
Matthias Clasen's avatar
Matthias Clasen committed
1363
                                          gboolean             setting)
1364
{
1365
  g_return_if_fail (GTK_IS_APP_CHOOSER_WIDGET (self));
1366 1367 1368 1369 1370 1371 1372

  if (self->priv->show_fallback != setting)
    {
      self->priv->show_fallback = setting;

      g_object_notify (G_OBJECT (self), "show-fallback");

1373
      gtk_app_chooser_refresh (GTK_APP_CHOOSER (self));
1374 1375 1376
    }
}

Matthias Clasen's avatar
Matthias Clasen committed
1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387
/**
 * gtk_app_chooser_widget_get_show_fallback:
 * @self: a #GtkAppChooserWidget
 *
 * Returns the current value of the #GtkAppChooserWidget:show-fallback
 * property.
 *
 * Returns: the value of #GtkAppChooserWidget:show-fallback
 *
 * Since: 3.0
 */
1388
gboolean
1389
gtk_app_chooser_widget_get_show_fallback (GtkAppChooserWidget *self)
1390
{