gtkappchooserwidget.c 46.3 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
 *
 * 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
Javier Jardón's avatar
Javier Jardón committed
18
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
19 20 21 22 23 24
 *
 * Authors: Dave Camp <dave@novell.com>
 *          Alexander Larsson <alexl@redhat.com>
 *          Cosimo Cecchi <ccecchi@redhat.com>
 */

Matthias Clasen's avatar
Matthias Clasen committed
25
#include "config.h"
26

27
#include "gtkappchooserwidget.h"
28 29 30

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

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

Matthias Clasen's avatar
Matthias Clasen committed
46 47 48 49 50 51 52 53 54
/**
 * 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.
55 56 57 58 59 60 61 62 63 64 65 66 67
 *
 * #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
68 69
 */

70
struct _GtkAppChooserWidgetPrivate {
71 72
  GAppInfo *selected_app_info;

73 74
  gchar *content_type;
  gchar *default_text;
75 76 77 78 79 80

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

  GtkWidget *program_list;
  GtkListStore *program_list_store;

  GtkCellRenderer *padding_renderer;
};

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


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

enum {
  SIGNAL_APPLICATION_SELECTED,
  SIGNAL_APPLICATION_ACTIVATED,
118
  SIGNAL_POPULATE_POPUP,
119 120 121 122 123
  N_SIGNALS
};

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

124
static void gtk_app_chooser_widget_iface_init (GtkAppChooserIface *iface);
125

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

static void
131
refresh_and_emit_app_selected (GtkAppChooserWidget *self,
Matthias Clasen's avatar
Matthias Clasen committed
132
                               GtkTreeSelection    *selection)
133 134 135 136 137 138 139
{
  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
140
    gtk_tree_model_get (model, &iter, COLUMN_APP_INFO, &info, -1);
141 142 143 144

  if (info == NULL)
    return;

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

Matthias Clasen's avatar
Matthias Clasen committed
152 153
          self->priv->selected_app_info = info;
        }
154 155 156 157 158 159 160 161 162
    }
  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
163
                   self->priv->selected_app_info);
164 165
}

166 167
static GAppInfo *
get_app_info_for_event (GtkAppChooserWidget *self,
Matthias Clasen's avatar
Matthias Clasen committed
168
                        GdkEventButton      *event)
169 170 171 172 173 174 175 176
{
  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
177 178 179
                                      event->x, event->y,
                                      &path,
                                      NULL, NULL, NULL))
180 181 182 183 184 185 186 187 188 189 190 191
    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
192 193 194
                      COLUMN_APP_INFO, &info,
                      COLUMN_RECOMMENDED, &recommended,
                      -1);
195 196 197 198 199 200 201 202

  if (!recommended)
    g_clear_object (&info);

  return info;
}

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

209
  if (event->button == GDK_BUTTON_SECONDARY && event->type == GDK_BUTTON_PRESS)
210 211 212 213 214 215 216 217 218
    {
      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
219
        return FALSE;
220 221 222 223

      menu = gtk_menu_new ();

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

      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
233 234 235 236 237 238
        {
          /* 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);
        }
239 240 241 242 243 244 245

      g_list_free (children);
    }

  return FALSE;
}

246 247
static gboolean
path_is_heading (GtkTreeView *view,
Matthias Clasen's avatar
Matthias Clasen committed
248
                 GtkTreePath *path)
249 250 251 252 253 254 255 256
{
  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
257 258
                      COLUMN_HEADING, &res,
                      -1);
259 260 261 262

  return res;
}

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

272 273 274
  if (path_is_heading (view, path))
    return;

275 276 277 278 279
  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
280
                 self->priv->selected_app_info);
281 282 283
}

static gboolean
284
gtk_app_chooser_search_equal_func (GtkTreeModel *model,
Matthias Clasen's avatar
Matthias Clasen committed
285 286 287 288
                                   gint          column,
                                   const gchar  *key,
                                   GtkTreeIter  *iter,
                                   gpointer      user_data)
289
{
Matthias Clasen's avatar
Matthias Clasen committed
290 291 292 293
  gchar *normalized_key;
  gchar *name, *normalized_name;
  gchar *path, *normalized_path;
  gchar *basename, *normalized_basename;
294 295 296 297 298 299 300 301 302 303
  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
304 305 306
                          COLUMN_NAME, &name,
                          COLUMN_EXEC, &path,
                          -1);
307 308

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

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

Matthias Clasen's avatar
Matthias Clasen committed
316 317
          g_free (normalized_name);
        }
318 319

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

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

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

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

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

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

      return ret;
    }
  else
    {
      return TRUE;
    }
}

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

  /* 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
371 372 373 374 375 376
                      COLUMN_NAME, &a_name,
                      COLUMN_RECOMMENDED, &a_recommended,
                      COLUMN_FALLBACK, &a_fallback,
                      COLUMN_HEADING, &a_heading,
                      COLUMN_DEFAULT, &a_default,
                      -1);
377 378

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

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

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

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

412 413 414 415 416 417 418 419 420 421 422 423 424 425
  /* 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 */
426 427
  if (a_heading)
    {
428
      retval = -1;
429 430 431 432 433
      goto out;
    }

  if (b_heading)
    {
434
      retval = 1;
435 436 437
      goto out;
    }

438 439 440 441
  /* 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
442
        g_utf8_casefold (a_name, -1) : NULL;
443
      b_casefold = b_name != NULL ?
Matthias Clasen's avatar
Matthias Clasen committed
444
        g_utf8_casefold (b_name, -1) : NULL;
445

446
      retval = g_strcmp0 (a_casefold, b_casefold);
447

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

 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
461 462 463 464
                            GtkCellRenderer   *cell,
                            GtkTreeModel      *model,
                            GtkTreeIter       *iter,
                            gpointer           user_data)
465 466 467 468
{
  gboolean heading;

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

static gboolean
486
gtk_app_chooser_selection_func (GtkTreeSelection *selection,
Matthias Clasen's avatar
Matthias Clasen committed
487 488 489 490
                                GtkTreeModel     *model,
                                GtkTreePath      *path,
                                gboolean          path_currently_selected,
                                gpointer          user_data)
491 492 493 494 495 496
{
  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
497 498
                      COLUMN_HEADING, &heading,
                      -1);
499 500 501 502 503 504

  return !heading;
}

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

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

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

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

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

543
      if (!heading_added && show_headings)
Matthias Clasen's avatar
Matthias Clasen committed
544 545 546 547 548 549 550 551 552 553 554
        {
          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;
        }
555

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

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

568 569
      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
570 571
                          COLUMN_APP_INFO, app,
                          COLUMN_GICON, icon,
572
                          COLUMN_NAME, g_app_info_get_name (app),
Matthias Clasen's avatar
Matthias Clasen committed
573 574 575 576 577 578
                          COLUMN_DESC, app_string,
                          COLUMN_EXEC, g_app_info_get_executable (app),
                          COLUMN_HEADING, FALSE,
                          COLUMN_RECOMMENDED, recommended,
                          COLUMN_FALLBACK, fallback,
                          -1);
579

580 581
      retval = TRUE;

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

587
  g_free (bold_string);
588 589

  return retval;
590
}
591

592 593

static void
594
gtk_app_chooser_add_default (GtkAppChooserWidget *self,
Matthias Clasen's avatar
Matthias Clasen committed
595
                             GAppInfo            *app)
596 597 598 599 600 601 602 603 604 605 606
{
  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
607 608 609 610
                      COLUMN_HEADING_TEXT, string,
                      COLUMN_HEADING, TRUE,
                      COLUMN_DEFAULT, TRUE,
                      -1);
611 612 613

  g_free (string);

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

  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
627 628
                      COLUMN_APP_INFO, app,
                      COLUMN_GICON, icon,
629
                      COLUMN_NAME, g_app_info_get_name (app),
Matthias Clasen's avatar
Matthias Clasen committed
630 631 632 633 634
                      COLUMN_DESC, string,
                      COLUMN_EXEC, g_app_info_get_executable (app),
                      COLUMN_HEADING, FALSE,
                      COLUMN_DEFAULT, TRUE,
                      -1);
635 636 637 638 639 640 641

  g_free (string);

  if (unref_icon)
    g_object_unref (icon);
}

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

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

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

663 664
  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
665 666 667
                      COLUMN_HEADING_TEXT, string,
                      COLUMN_HEADING, TRUE,
                      -1);
668

Matthias Clasen's avatar
Matthias Clasen committed
669
  g_free (text);
670
}
671

672
static void
673
gtk_app_chooser_widget_select_first (GtkAppChooserWidget *self)
674 675 676 677 678 679 680 681 682 683 684
{
  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
685 686
                          COLUMN_APP_INFO, &info,
                          -1);
687 688

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

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

  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);
    }
}

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

717 718 719 720 721 722
  show_headings = TRUE;
  apps_added = FALSE;

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

723
  if (self->priv->show_default && self->priv->content_type)
724 725 726 727
    {
      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
728 729 730 731 732
        {
          gtk_app_chooser_add_default (self, default_app);
          apps_added = TRUE;
          exclude_apps = g_list_prepend (exclude_apps, default_app);
        }
733 734
    }

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

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

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

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

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

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

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

777 778
  if (!apps_added)
    add_no_applications_label (self);
779

780
  gtk_app_chooser_widget_select_first (self);
781 782 783 784

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

785 786
  if (all_applications != NULL)
    g_list_free_full (all_applications, g_object_unref);
787

788 789
  if (recommended_apps != NULL)
    g_list_free_full (recommended_apps, g_object_unref);
790

791 792
  if (fallback_apps != NULL)
    g_list_free_full (fallback_apps, g_object_unref);
793

794 795
  if (exclude_apps != NULL)
    g_list_free (exclude_apps);
796 797 798
}

static void
799
gtk_app_chooser_widget_add_items (GtkAppChooserWidget *self)
800 801 802 803 804 805 806
{
  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
807 808 809 810 811 812 813 814 815 816
                                                       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);
817 818
  sort = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (self->priv->program_list_store));

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

  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
840 841
                "xpad", self->priv->show_all ? 0 : 6,
                NULL);
842 843 844 845 846 847
  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
848 849 850
                                       "markup", COLUMN_HEADING_TEXT,
                                       "visible", COLUMN_HEADING,
                                       NULL);
851
  g_object_set (renderer,
Matthias Clasen's avatar
Matthias Clasen committed
852 853 854 855 856
                "ypad", 6,
                "xpad", 0,
                "wrap-width", 350,
                "wrap-mode", PANGO_WRAP_WORD,
                NULL);
857 858 859 860 861

  /* 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
862 863
                                           padding_cell_renderer_func,
                                           NULL, NULL);
864 865 866 867 868

  /* 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
869 870
                                       "gicon", COLUMN_GICON,
                                       NULL);
871
  g_object_set (renderer,
872
                "stock-size", GTK_ICON_SIZE_MENU,
Matthias Clasen's avatar
Matthias Clasen committed
873
                NULL);
874 875 876 877 878

  /* 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
879 880
                                       "markup", COLUMN_DESC,
                                       NULL);
881
  g_object_set (renderer,
Matthias Clasen's avatar
Matthias Clasen committed
882 883 884
                "ellipsize", PANGO_ELLIPSIZE_END,
                "ellipsize-set", TRUE,
                NULL);
885
  
886 887
  gtk_tree_view_column_set_sort_column_id (column, COLUMN_NAME);
  gtk_tree_view_append_column (GTK_TREE_VIEW (self->priv->program_list), column);
888 889

  /* populate the widget */
890
  gtk_app_chooser_widget_real_add_items (self);
891 892 893
}

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

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

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

  switch (property_id)
    {
    case PROP_CONTENT_TYPE:
      g_value_set_string (value, self->priv->content_type);
      break;
943 944 945
    case PROP_SHOW_DEFAULT:
      g_value_set_boolean (value, self->priv->show_default);
      break;
946 947 948 949 950 951 952 953 954 955 956 957
    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;
958 959 960
    case PROP_DEFAULT_TEXT:
      g_value_set_string (value, self->priv->default_text);
      break;
961 962 963 964 965 966 967
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

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

972 973
  if (G_OBJECT_CLASS (gtk_app_chooser_widget_parent_class)->constructed != NULL)
    G_OBJECT_CLASS (gtk_app_chooser_widget_parent_class)->constructed (object);
974

975
  gtk_app_chooser_widget_add_items (self);
976 977 978
}

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

  g_free (self->priv->content_type);
984
  g_free (self->priv->default_text);
985

986
  G_OBJECT_CLASS (gtk_app_chooser_widget_parent_class)->finalize (object);
987 988 989
}

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

994
  g_clear_object (&self->priv->selected_app_info);
995

996
  G_OBJECT_CLASS (gtk_app_chooser_widget_parent_class)->dispose (object);
997 998 999
}

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

  gobject_class = G_OBJECT_CLASS (klass);
1006 1007 1008 1009 1010
  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;
1011 1012 1013

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

Matthias Clasen's avatar
Matthias Clasen committed
1014 1015 1016 1017 1018 1019 1020 1021
  /**
   * 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.
   */
1022
  pspec = g_param_spec_boolean ("show-default",
Matthias Clasen's avatar
Matthias Clasen committed
1023 1024 1025 1026
                                P_("Show default app"),
                                P_("Whether the widget should show the default application"),
                                FALSE,
                                G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
1027 1028
  g_object_class_install_property (gobject_class, PROP_SHOW_DEFAULT, pspec);

Matthias Clasen's avatar
Matthias Clasen committed
1029 1030 1031
  /**
   * GtkAppChooserWidget:show-recommended:
   *
1032 1033 1034 1035
   * 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
1036
   */
1037
  pspec = g_param_spec_boolean ("show-recommended",
Matthias Clasen's avatar
Matthias Clasen committed
1038 1039 1040 1041
                                P_("Show recommended apps"),
                                P_("Whether the widget should show recommended applications"),
                                TRUE,
                                G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
1042 1043
  g_object_class_install_property (gobject_class, PROP_SHOW_RECOMMENDED, pspec);

Matthias Clasen's avatar
Matthias Clasen committed
1044 1045 1046
  /**
   * GtkAppChooserWidget:show-fallback:
   *
1047 1048 1049 1050
   * 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
1051
   */
1052
  pspec = g_param_spec_boolean ("show-fallback",
Matthias Clasen's avatar
Matthias Clasen committed
1053 1054 1055 1056
                                P_("Show fallback apps"),
                                P_("Whether the widget should show fallback applications"),
                                FALSE,
                                G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
1057 1058
  g_object_class_install_property (gobject_class, PROP_SHOW_FALLBACK, pspec);

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

Matthias Clasen's avatar
Matthias Clasen committed
1072 1073 1074
  /**
   * GtkAppChooserWidget:show-all:
   *
1075 1076 1077
   * 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
1078
   */
1079
  pspec = g_param_spec_boolean ("show-all",
Matthias Clasen's avatar
Matthias Clasen committed
1080 1081 1082 1083
                                P_("Show all apps"),
                                P_("Whether the widget should show all applications"),
                                FALSE,
                                G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
1084 1085
  g_object_class_install_property (gobject_class, PROP_SHOW_ALL, pspec);

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

Cosimo Cecchi's avatar
Cosimo Cecchi committed
1101 1102 1103 1104 1105 1106 1107
  /**
   * 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.
   */
1108 1109
  signals[SIGNAL_APPLICATION_SELECTED] =
    g_signal_new ("application-selected",
Matthias Clasen's avatar
Matthias Clasen committed
1110 1111 1112 1113 1114 1115 1116
                  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);
1117

Cosimo Cecchi's avatar
Cosimo Cecchi committed
1118 1119 1120 1121 1122 1123
  /**
   * 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.
1124
   *
Cosimo Cecchi's avatar
Cosimo Cecchi committed
1125 1126 1127 1128
   * 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.
   */
1129 1130
  signals[SIGNAL_APPLICATION_ACTIVATED] =
    g_signal_new ("application-activated",
Matthias Clasen's avatar
Matthias Clasen committed
1131 1132 1133 1134 1135 1136 1137
                  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);
1138

Cosimo Cecchi's avatar
Cosimo Cecchi committed
1139 1140 1141 1142 1143 1144 1145 1146
  /**
   * 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
1147 1148
   * 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
1149
   */
1150 1151
  signals[SIGNAL_POPULATE_POPUP] =
    g_signal_new ("populate-popup",
Matthias Clasen's avatar
Matthias Clasen committed
1152 1153 1154 1155 1156 1157 1158
                  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);
1159

1160
  g_type_class_add_private (klass, sizeof (GtkAppChooserWidgetPrivate));
1161 1162 1163
}

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

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

  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
1176
                                       GTK_SHADOW_IN);
1177
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
Matthias Clasen's avatar
Matthias Clasen committed
1178 1179
                                  GTK_POLICY_NEVER,
                                  GTK_POLICY_AUTOMATIC);
1180
  gtk_widget_show (scrolled_window);
1181 1182 1183

  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
1184
                                     FALSE);
1185 1186
  gtk_container_add (GTK_CONTAINER (scrolled_window), self->priv->program_list);
  gtk_box_pack_start (GTK_BOX (self), scrolled_window, TRUE, TRUE, 0);
1187
  gtk_widget_show (self->priv->program_list);
1188 1189

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

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

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

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

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

1220 1221 1222 1223 1224 1225
  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
1226 1227
                    "visible", !self->priv->show_all,
                    NULL);
1228

1229
      gtk_app_chooser_widget_real_add_items (self);
1230 1231 1232
    }
}

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

Matthias Clasen's avatar
Matthias Clasen committed
1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250
/**
 * 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
 */
1251
GtkWidget *
1252
gtk_app_chooser_widget_new (const gchar *content_type)
1253
{
1254
  return g_object_new (GTK_TYPE_APP_CHOOSER_WIDGET,
Matthias Clasen's avatar
Matthias Clasen committed
1255 1256
                       "content-type", content_type,
                       NULL);
1257 1258
}

Matthias Clasen's avatar
Matthias Clasen committed
1259 1260 1261 1262 1263 1264 1265 1266 1267 1268
/**
 * 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
 */
1269
void
1270
gtk_app_chooser_widget_set_show_default (GtkAppChooserWidget *self,
Matthias Clasen's avatar
Matthias Clasen committed
1271
                                         gboolean             setting)
1272
{
1273
  g_return_if_fail (GTK_IS_APP_CHOOSER_WIDGET (self));
1274 1275 1276 1277 1278 1279 1280

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

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

1281
      gtk_app_chooser_refresh (GTK_APP_CHOOSER (self));
1282 1283 1284
    }
}

Matthias Clasen's avatar
Matthias Clasen committed
1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295
/**
 * 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
 */
1296
gboolean
1297
gtk_app_chooser_widget_get_show_default (GtkAppChooserWidget *self)
1298
{
1299
  g_return_val_if_fail (GTK_IS_APP_CHOOSER_WIDGET (self), FALSE);
1300 1301 1302 1303

  return self->priv->show_default;
}

Matthias Clasen's avatar
Matthias Clasen committed
1304 1305 1306 1307 1308 1309 1310 1311 1312 1313
/**
 * 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
 */
1314
void
1315
gtk_app_chooser_widget_set_show_recommended (GtkAppChooserWidget *self,
Matthias Clasen's avatar
Matthias Clasen committed
1316
                                             gboolean             setting)
1317
{
1318
  g_return_if_fail (GTK_IS_APP_CHOOSER_WIDGET (self));
1319

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

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

1326
      gtk_app_chooser_refresh (GTK_APP_CHOOSER (self));
1327 1328 1329
    }
}

Matthias Clasen's avatar
Matthias Clasen committed
1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340
/**
 * 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
 */
1341
gboolean
1342
gtk_app_chooser_widget_get_show_recommended (GtkAppChooserWidget *self)
1343
{
1344
  g_return_val_if_fail (GTK_IS_APP_CHOOSER_WIDGET (self), FALSE);
1345 1346 1347 1348

  return self->priv->show_recommended;
}

Matthias Clasen's avatar
Matthias Clasen committed
1349 1350 1351 1352 1353 1354 1355 1356 1357 1358
/**
 * 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
 */
1359
void
1360
gtk_app_chooser_widget_set_show_fallback (GtkAppChooserWidget *self,
Matthias Clasen's avatar
Matthias Clasen committed
1361
                                          gboolean             setting)
1362
{
1363
  g_return_if_fail (GTK_IS_APP_CHOOSER_WIDGET (self));
1364 1365 1366 1367 1368 1369 1370

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

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

1371
      gtk_app_chooser_refresh (GTK_APP_CHOOSER (self));
1372 1373 1374
    }
}

Matthias Clasen's avatar
Matthias Clasen committed
1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385
/**
 * 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
 */
1386
gboolean
1387
gtk_app_chooser_widget_get_show_fallback (GtkAppChooserWidget *self)