~ubuntu-branches/ubuntu/wily/gnome-software/wily

« back to all changes in this revision

Viewing changes to src/gs-shell-installed.c

  • Committer: Package Import Robot
  • Author(s): Laurent Bigonville
  • Date: 2014-10-11 11:04:58 UTC
  • Revision ID: package-import@ubuntu.com-20141011110458-0qsyec8s0lsjevdv
Tags: upstream-3.14.0
ImportĀ upstreamĀ versionĀ 3.14.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
 
2
 *
 
3
 * Copyright (C) 2013 Richard Hughes <richard@hughsie.com>
 
4
 * Copyright (C) 2013 Matthias Clasen <mclasen@redhat.com>
 
5
 *
 
6
 * Licensed under the GNU General Public License Version 2
 
7
 *
 
8
 * This program is free software; you can redistribute it and/or modify
 
9
 * it under the terms of the GNU General Public License as published by
 
10
 * the Free Software Foundation; either version 2 of the License, or
 
11
 * (at your option) any later version.
 
12
 *
 
13
 * This program is distributed in the hope that it will be useful,
 
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
16
 * GNU General Public License for more details.
 
17
 *
 
18
 * You should have received a copy of the GNU General Public License
 
19
 * along with this program; if not, write to the Free Software
 
20
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
21
 */
 
22
 
 
23
#include "config.h"
 
24
 
 
25
#include <string.h>
 
26
#include <glib/gi18n.h>
 
27
#include <appstream-glib.h>
 
28
 
 
29
#include "gs-shell.h"
 
30
#include "gs-shell-installed.h"
 
31
#include "gs-app.h"
 
32
#include "gs-utils.h"
 
33
#include "gs-app-row.h"
 
34
#include "gs-app-folder-dialog.h"
 
35
#include "gs-folders.h"
 
36
 
 
37
static void     gs_shell_installed_finalize     (GObject        *object);
 
38
 
 
39
struct GsShellInstalledPrivate
 
40
{
 
41
        GsPluginLoader          *plugin_loader;
 
42
        GtkBuilder              *builder;
 
43
        GCancellable            *cancellable;
 
44
        GtkSizeGroup            *sizegroup_image;
 
45
        GtkSizeGroup            *sizegroup_name;
 
46
        gboolean                 cache_valid;
 
47
        gboolean                 waiting;
 
48
        GsShell                 *shell;
 
49
        gboolean                 selection_mode;
 
50
 
 
51
        GtkWidget               *bottom_install;
 
52
        GtkWidget               *button_folder_add;
 
53
        GtkWidget               *button_folder_move;
 
54
        GtkWidget               *button_folder_remove;
 
55
        GtkWidget               *list_box_install;
 
56
        GtkWidget               *scrolledwindow_install;
 
57
        GtkWidget               *spinner_install;
 
58
        GtkWidget               *stack_install;
 
59
};
 
60
 
 
61
G_DEFINE_TYPE_WITH_PRIVATE (GsShellInstalled, gs_shell_installed, GTK_TYPE_BIN)
 
62
 
 
63
static void gs_shell_installed_pending_apps_changed_cb (GsPluginLoader *plugin_loader,
 
64
                                                        GsShellInstalled *shell_installed);
 
65
static void set_selection_mode (GsShellInstalled *shell_installed, gboolean selection_mode);
 
66
 
 
67
/**
 
68
 * gs_shell_installed_invalidate:
 
69
 **/
 
70
void
 
71
gs_shell_installed_invalidate (GsShellInstalled *shell_installed)
 
72
{
 
73
        shell_installed->priv->cache_valid = FALSE;
 
74
}
 
75
 
 
76
static void
 
77
gs_shell_installed_app_row_activated_cb (GtkListBox *list_box,
 
78
                                         GtkListBoxRow *row,
 
79
                                         GsShellInstalled *shell_installed)
 
80
{
 
81
        if (shell_installed->priv->selection_mode) {
 
82
                gboolean selected;
 
83
                selected = gs_app_row_get_selected (GS_APP_ROW (row));
 
84
                gs_app_row_set_selected (GS_APP_ROW (row), !selected);
 
85
        } else {
 
86
                GsApp *app;
 
87
                app = gs_app_row_get_app (GS_APP_ROW (row));
 
88
                gs_shell_show_app (shell_installed->priv->shell, app);
 
89
        }
 
90
}
 
91
 
 
92
typedef struct {
 
93
        GsAppRow                *app_row;
 
94
        GsShellInstalled        *shell_installed;
 
95
} GsShellInstalledHelper;
 
96
 
 
97
static void
 
98
row_unrevealed (GObject *row, GParamSpec *pspec, gpointer data)
 
99
{
 
100
        GtkWidget *list;
 
101
 
 
102
        list = gtk_widget_get_parent (GTK_WIDGET (row));
 
103
        gtk_container_remove (GTK_CONTAINER (list), GTK_WIDGET (row));
 
104
}
 
105
 
 
106
/**
 
107
 * gs_shell_installed_app_removed_cb:
 
108
 **/
 
109
static void
 
110
gs_shell_installed_app_removed_cb (GObject *source,
 
111
                                   GAsyncResult *res,
 
112
                                   gpointer user_data)
 
113
{
 
114
        GError *error = NULL;
 
115
        GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source);
 
116
        GsShellInstalledHelper *helper = (GsShellInstalledHelper *) user_data;
 
117
        GsShellInstalledPrivate *priv = helper->shell_installed->priv;
 
118
        GsApp *app;
 
119
        gboolean ret;
 
120
 
 
121
        ret = gs_plugin_loader_app_action_finish (plugin_loader,
 
122
                                                  res,
 
123
                                                  &error);
 
124
        if (!ret) {
 
125
                app = gs_app_row_get_app (helper->app_row);
 
126
                g_warning ("failed to remove %s: %s",
 
127
                           gs_app_get_id (app),
 
128
                           error->message);
 
129
                gs_app_notify_failed_modal (app,
 
130
                                            gs_shell_get_window (priv->shell),
 
131
                                            GS_PLUGIN_LOADER_ACTION_REMOVE,
 
132
                                            error);
 
133
                g_error_free (error);
 
134
        } else {
 
135
                /* remove from the list */
 
136
                app = gs_app_row_get_app (helper->app_row);
 
137
                g_debug ("removed %s", gs_app_get_id (app));
 
138
                gs_app_row_unreveal (helper->app_row);
 
139
                g_signal_connect (helper->app_row, "unrevealed",
 
140
                                  G_CALLBACK (row_unrevealed), NULL);
 
141
        }
 
142
 
 
143
        g_object_unref (helper->app_row);
 
144
        g_object_unref (helper->shell_installed);
 
145
        g_free (helper);
 
146
}
 
147
 
 
148
/**
 
149
 * gs_shell_installed_app_remove_cb:
 
150
 **/
 
151
static void
 
152
gs_shell_installed_app_remove_cb (GsAppRow *app_row,
 
153
                                  GsShellInstalled *shell_installed)
 
154
{
 
155
        GsApp *app;
 
156
        GsShellInstalledPrivate *priv = shell_installed->priv;
 
157
        GString *markup;
 
158
        GtkResponseType response;
 
159
        GtkWidget *dialog;
 
160
        GsShellInstalledHelper *helper;
 
161
 
 
162
        markup = g_string_new ("");
 
163
        app = gs_app_row_get_app (app_row);
 
164
        g_string_append_printf (markup,
 
165
                                /* TRANSLATORS: this is a prompt message, and
 
166
                                 * '%s' is an application summary, e.g. 'GNOME Clocks' */
 
167
                                _("Are you sure you want to remove %s?"),
 
168
                                gs_app_get_name (app));
 
169
        g_string_prepend (markup, "<b>");
 
170
        g_string_append (markup, "</b>");
 
171
        dialog = gtk_message_dialog_new (gs_shell_get_window (priv->shell),
 
172
                                         GTK_DIALOG_MODAL,
 
173
                                         GTK_MESSAGE_QUESTION,
 
174
                                         GTK_BUTTONS_CANCEL,
 
175
                                         NULL);
 
176
        gtk_message_dialog_set_markup (GTK_MESSAGE_DIALOG (dialog), markup->str);
 
177
        gtk_message_dialog_format_secondary_markup (GTK_MESSAGE_DIALOG (dialog),
 
178
                                                    /* TRANSLATORS: longer dialog text */
 
179
                                                    _("%s will be removed, and you will have to install it to use it again."),
 
180
                                                    gs_app_get_name (app));
 
181
        /* TRANSLATORS: this is button text to remove the application */
 
182
        gtk_dialog_add_button (GTK_DIALOG (dialog), _("Remove"), GTK_RESPONSE_OK);
 
183
        if (gs_app_get_state (app) == AS_APP_STATE_QUEUED_FOR_INSTALL)
 
184
                response = GTK_RESPONSE_OK; /* pending install */
 
185
        else
 
186
                response = gtk_dialog_run (GTK_DIALOG (dialog));
 
187
        if (response == GTK_RESPONSE_OK) {
 
188
                g_debug ("removing %s", gs_app_get_id (app));
 
189
                helper = g_new0 (GsShellInstalledHelper, 1);
 
190
                helper->shell_installed = g_object_ref (shell_installed);
 
191
                helper->app_row = g_object_ref (app_row);
 
192
                gs_plugin_loader_app_action_async (priv->plugin_loader,
 
193
                                                   app,
 
194
                                                   GS_PLUGIN_LOADER_ACTION_REMOVE,
 
195
                                                   priv->cancellable,
 
196
                                                   gs_shell_installed_app_removed_cb,
 
197
                                                   helper);
 
198
        }
 
199
        g_string_free (markup, TRUE);
 
200
        gtk_widget_destroy (dialog);
 
201
}
 
202
 
 
203
static gboolean
 
204
gs_shell_installed_invalidate_sort_idle (gpointer user_data)
 
205
{
 
206
        GsShellInstalled *shell = GS_SHELL_INSTALLED (user_data);
 
207
 
 
208
        gtk_list_box_invalidate_sort (GTK_LIST_BOX (shell->priv->list_box_install));
 
209
 
 
210
        g_object_unref (shell);
 
211
        return G_SOURCE_REMOVE;
 
212
}
 
213
 
 
214
/**
 
215
 * gs_shell_installed_notify_state_changed_cb:
 
216
 **/
 
217
static void
 
218
gs_shell_installed_notify_state_changed_cb (GsApp *app,
 
219
                                            GParamSpec *pspec,
 
220
                                            GsShellInstalled *shell)
 
221
{
 
222
        g_idle_add (gs_shell_installed_invalidate_sort_idle, g_object_ref (shell));
 
223
}
 
224
 
 
225
static void selection_changed (GsShellInstalled *shell);
 
226
 
 
227
static void
 
228
gs_shell_installed_add_app (GsShellInstalled *shell, GsApp *app)
 
229
{
 
230
        GsShellInstalledPrivate *priv = shell->priv;
 
231
        GtkWidget *app_row;
 
232
 
 
233
        app_row = gs_app_row_new ();
 
234
        gs_app_row_set_colorful (GS_APP_ROW (app_row), FALSE);
 
235
        g_signal_connect (app_row, "button-clicked",
 
236
                          G_CALLBACK (gs_shell_installed_app_remove_cb), shell);
 
237
        g_signal_connect_object (app, "notify::state",
 
238
                                 G_CALLBACK (gs_shell_installed_notify_state_changed_cb),
 
239
                                 shell, 0);
 
240
        g_signal_connect_swapped (app_row, "notify::selected",
 
241
                                  G_CALLBACK (selection_changed), shell);
 
242
        gs_app_row_set_app (GS_APP_ROW (app_row), app);
 
243
        gtk_container_add (GTK_CONTAINER (priv->list_box_install), app_row);
 
244
        gs_app_row_set_size_groups (GS_APP_ROW (app_row),
 
245
                                    priv->sizegroup_image,
 
246
                                    priv->sizegroup_name);
 
247
 
 
248
        gs_app_row_set_selectable (GS_APP_ROW (app_row),
 
249
                                   priv->selection_mode);
 
250
 
 
251
        gtk_widget_show (app_row);
 
252
}
 
253
 
 
254
/**
 
255
 * gs_shell_installed_get_installed_cb:
 
256
 **/
 
257
static void
 
258
gs_shell_installed_get_installed_cb (GObject *source_object,
 
259
                                     GAsyncResult *res,
 
260
                                     gpointer user_data)
 
261
{
 
262
        GError *error = NULL;
 
263
        GList *l;
 
264
        GList *list;
 
265
        GsApp *app;
 
266
        GsShellInstalled *shell_installed = GS_SHELL_INSTALLED (user_data);
 
267
        GsShellInstalledPrivate *priv = shell_installed->priv;
 
268
        GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source_object);
 
269
 
 
270
        gs_stop_spinner (GTK_SPINNER (priv->spinner_install));
 
271
        gtk_stack_set_visible_child_name (GTK_STACK (priv->stack_install), "view");
 
272
 
 
273
        priv->waiting = FALSE;
 
274
        priv->cache_valid = TRUE;
 
275
 
 
276
        list = gs_plugin_loader_get_installed_finish (plugin_loader,
 
277
                                                      res,
 
278
                                                      &error);
 
279
        if (list == NULL) {
 
280
                g_warning ("failed to get installed apps: %s", error->message);
 
281
                g_error_free (error);
 
282
                goto out;
 
283
        }
 
284
        for (l = list; l != NULL; l = l->next) {
 
285
                app = GS_APP (l->data);
 
286
                gs_shell_installed_add_app (shell_installed, app);
 
287
        }
 
288
out:
 
289
        gs_plugin_list_free (list);
 
290
        gs_shell_installed_pending_apps_changed_cb (plugin_loader, shell_installed);
 
291
}
 
292
 
 
293
/**
 
294
 * gs_shell_installed_load:
 
295
 */
 
296
static void
 
297
gs_shell_installed_load (GsShellInstalled *shell_installed)
 
298
{
 
299
        GsShellInstalledPrivate *priv = shell_installed->priv;
 
300
 
 
301
        if (priv->waiting)
 
302
                return;
 
303
        priv->waiting = TRUE;
 
304
 
 
305
        /* remove old entries */
 
306
        gs_container_remove_all (GTK_CONTAINER (priv->list_box_install));
 
307
 
 
308
        /* get popular apps */
 
309
        gs_plugin_loader_get_installed_async (priv->plugin_loader,
 
310
                                              GS_PLUGIN_REFINE_FLAGS_DEFAULT |
 
311
                                              GS_PLUGIN_REFINE_FLAGS_REQUIRE_HISTORY |
 
312
                                              GS_PLUGIN_REFINE_FLAGS_REQUIRE_SETUP_ACTION |
 
313
                                              GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION |
 
314
                                              GS_PLUGIN_REFINE_FLAGS_REQUIRE_DESCRIPTION |
 
315
                                              GS_PLUGIN_REFINE_FLAGS_REQUIRE_RATING,
 
316
                                              priv->cancellable,
 
317
                                              gs_shell_installed_get_installed_cb,
 
318
                                              shell_installed);
 
319
        gs_start_spinner (GTK_SPINNER (priv->spinner_install));
 
320
        gtk_stack_set_visible_child_name (GTK_STACK (priv->stack_install), "spinner");
 
321
}
 
322
 
 
323
/**
 
324
 * gs_shell_installed_reload:
 
325
 */
 
326
void
 
327
gs_shell_installed_reload (GsShellInstalled *shell_installed)
 
328
{
 
329
        gs_shell_installed_invalidate (shell_installed);
 
330
        gs_shell_installed_load (shell_installed);
 
331
}
 
332
 
 
333
/**
 
334
 * gs_shell_installed_switch_to:
 
335
 **/
 
336
void
 
337
gs_shell_installed_switch_to (GsShellInstalled *shell_installed, gboolean scroll_up)
 
338
{
 
339
        GsShellInstalledPrivate *priv = shell_installed->priv;
 
340
        GtkWidget *widget;
 
341
 
 
342
        if (gs_shell_get_mode (priv->shell) != GS_SHELL_MODE_INSTALLED) {
 
343
                g_warning ("Called switch_to(installed) when in mode %s",
 
344
                           gs_shell_get_mode_string (priv->shell));
 
345
                return;
 
346
        }
 
347
 
 
348
        set_selection_mode (shell_installed, FALSE);
 
349
        widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "buttonbox_main"));
 
350
        gtk_widget_show (widget);
 
351
 
 
352
        widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "button_select"));
 
353
        gtk_widget_show (widget);
 
354
 
 
355
        gtk_list_box_invalidate_sort (GTK_LIST_BOX (priv->list_box_install));
 
356
 
 
357
        if (scroll_up) {
 
358
                GtkAdjustment *adj;
 
359
                adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (priv->scrolledwindow_install));
 
360
                gtk_adjustment_set_value (adj, gtk_adjustment_get_lower (adj));
 
361
        }
 
362
        if (gs_shell_get_mode (priv->shell) == GS_SHELL_MODE_INSTALLED) {
 
363
                gs_grab_focus_when_mapped (priv->scrolledwindow_install);
 
364
        }
 
365
 
 
366
        /* no need to refresh */
 
367
        if (priv->cache_valid)
 
368
                return;
 
369
 
 
370
        gs_shell_installed_load (shell_installed);
 
371
}
 
372
 
 
373
/**
 
374
 * gs_shell_installed_get_app_sort_key:
 
375
 *
 
376
 * Get a sort key to achive this:
 
377
 *
 
378
 * 1. state:installing applications
 
379
 * 2. state:removing applications
 
380
 * 3. kind:normal applications
 
381
 * 4. kind:system applications
 
382
 *
 
383
 * Within each of these groups, they are sorted by the install date and then
 
384
 * by name.
 
385
 **/
 
386
static gchar *
 
387
gs_shell_installed_get_app_sort_key (GsApp *app)
 
388
{
 
389
        GString *key;
 
390
        gchar *casefolded_name;
 
391
 
 
392
        key = g_string_sized_new (64);
 
393
 
 
394
        /* sort installed, removing, other */
 
395
        switch (gs_app_get_state (app)) {
 
396
        case AS_APP_STATE_INSTALLING:
 
397
        case AS_APP_STATE_QUEUED_FOR_INSTALL:
 
398
                g_string_append (key, "1:");
 
399
                break;
 
400
        case AS_APP_STATE_REMOVING:
 
401
                g_string_append (key, "2:");
 
402
                break;
 
403
        default:
 
404
                g_string_append (key, "3:");
 
405
                break;
 
406
        }
 
407
 
 
408
        /* sort desktop files, then addons */
 
409
        switch (gs_app_get_id_kind (app)) {
 
410
        case AS_ID_KIND_DESKTOP:
 
411
        case AS_ID_KIND_WEB_APP:
 
412
                g_string_append (key, "1:");
 
413
                break;
 
414
        default:
 
415
                g_string_append (key, "2:");
 
416
                break;
 
417
        }
 
418
 
 
419
        /* sort normal, system, other */
 
420
        switch (gs_app_get_kind (app)) {
 
421
        case GS_APP_KIND_NORMAL:
 
422
                g_string_append (key, "1:");
 
423
                break;
 
424
        case GS_APP_KIND_SYSTEM:
 
425
                g_string_append (key, "2:");
 
426
                break;
 
427
        default:
 
428
                g_string_append (key, "3:");
 
429
                break;
 
430
        }
 
431
 
 
432
        /* finally, sort by short name */
 
433
        casefolded_name = g_utf8_casefold (gs_app_get_name (app), -1);
 
434
        g_string_append (key, casefolded_name);
 
435
        g_free (casefolded_name);
 
436
 
 
437
        return g_string_free (key, FALSE);
 
438
}
 
439
 
 
440
/**
 
441
 * gs_shell_installed_sort_func:
 
442
 **/
 
443
static gint
 
444
gs_shell_installed_sort_func (GtkListBoxRow *a,
 
445
                              GtkListBoxRow *b,
 
446
                              gpointer user_data)
 
447
{
 
448
        GsApp *a1, *a2;
 
449
        gchar *key1 = NULL;
 
450
        gchar *key2 = NULL;
 
451
        gint retval = 0;
 
452
 
 
453
        /* check valid */
 
454
        if (!GTK_IS_BIN(a) || !GTK_IS_BIN(b)) {
 
455
                g_warning ("GtkListBoxRow not valid");
 
456
                goto out;
 
457
        }
 
458
 
 
459
        a1 = gs_app_row_get_app (GS_APP_ROW (a));
 
460
        a2 = gs_app_row_get_app (GS_APP_ROW (b));
 
461
        key1 = gs_shell_installed_get_app_sort_key (a1);
 
462
        key2 = gs_shell_installed_get_app_sort_key (a2);
 
463
 
 
464
        /* compare the keys according to the algorithm above */
 
465
        retval = g_strcmp0 (key1, key2);
 
466
out:
 
467
        g_free (key1);
 
468
        g_free (key2);
 
469
        return retval;
 
470
}
 
471
 
 
472
/**
 
473
 * gs_shell_installed_is_addon_id_kind
 
474
 **/
 
475
static gboolean
 
476
gs_shell_installed_is_addon_id_kind (GsApp *app)
 
477
{
 
478
        AsIdKind id_kind;
 
479
        id_kind = gs_app_get_id_kind (app);
 
480
        if (id_kind == AS_ID_KIND_DESKTOP)
 
481
                return FALSE;
 
482
        if (id_kind == AS_ID_KIND_WEB_APP)
 
483
                return FALSE;
 
484
        return TRUE;
 
485
}
 
486
 
 
487
static gboolean
 
488
gs_shell_installed_is_system_application (GsApp *app)
 
489
{
 
490
        if (gs_app_get_id_kind (app) == AS_ID_KIND_DESKTOP &&
 
491
            gs_app_get_kind (app) == GS_APP_KIND_SYSTEM)
 
492
                return TRUE;
 
493
        return FALSE;
 
494
}
 
495
 
 
496
/**
 
497
 * gs_shell_installed_list_header_func
 
498
 **/
 
499
static void
 
500
gs_shell_installed_list_header_func (GtkListBoxRow *row,
 
501
                                     GtkListBoxRow *before,
 
502
                                     gpointer user_data)
 
503
{
 
504
        GtkStyleContext *context;
 
505
        GtkWidget *header;
 
506
 
 
507
        /* reset */
 
508
        gtk_list_box_row_set_header (row, NULL);
 
509
        if (before == NULL)
 
510
                return;
 
511
 
 
512
        if (!gs_shell_installed_is_system_application (gs_app_row_get_app (GS_APP_ROW (before))) &&
 
513
            gs_shell_installed_is_system_application (gs_app_row_get_app (GS_APP_ROW (row)))) {
 
514
                /* TRANSLATORS: This is the header dividing the normal
 
515
                 * applications and the system ones */
 
516
                header = gtk_label_new (_("System Applications"));
 
517
                g_object_set (header,
 
518
                              "xalign", 0.0,
 
519
                              NULL);
 
520
                context = gtk_widget_get_style_context (header);
 
521
                gtk_style_context_add_class (context, "header-label");
 
522
        } else if (!gs_shell_installed_is_addon_id_kind (gs_app_row_get_app (GS_APP_ROW (before))) &&
 
523
                   gs_shell_installed_is_addon_id_kind (gs_app_row_get_app (GS_APP_ROW (row)))) {
 
524
                /* TRANSLATORS: This is the header dividing the normal
 
525
                 * applications and the addons */
 
526
                header = gtk_label_new (_("Add-ons"));
 
527
                g_object_set (header,
 
528
                              "xalign", 0.0,
 
529
                              NULL);
 
530
                context = gtk_widget_get_style_context (header);
 
531
                gtk_style_context_add_class (context, "header-label");
 
532
        } else {
 
533
                header = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
 
534
        }
 
535
        gtk_list_box_row_set_header (row, header);
 
536
}
 
537
 
 
538
static gboolean
 
539
gs_shell_installed_has_app (GsShellInstalled *shell_installed,
 
540
                            GsApp *app)
 
541
{
 
542
        GsShellInstalledPrivate *priv = shell_installed->priv;
 
543
        GList *children, *l;
 
544
        gboolean ret = FALSE;
 
545
 
 
546
        children = gtk_container_get_children (GTK_CONTAINER (priv->list_box_install));
 
547
        for (l = children; l; l = l->next) {
 
548
                GsAppRow *app_row = GS_APP_ROW (l->data);
 
549
                if (gs_app_row_get_app (app_row) == app) {
 
550
                        ret = TRUE;
 
551
                        break;
 
552
                }
 
553
        }
 
554
        g_list_free (children);
 
555
 
 
556
        return ret;
 
557
}
 
558
 
 
559
/**
 
560
 * gs_shell_installed_pending_apps_changed_cb:
 
561
 */
 
562
static void
 
563
gs_shell_installed_pending_apps_changed_cb (GsPluginLoader *plugin_loader,
 
564
                                            GsShellInstalled *shell_installed)
 
565
{
 
566
        GPtrArray *pending;
 
567
        GsApp *app;
 
568
        GtkWidget *widget;
 
569
        gchar *label;
 
570
        guint i;
 
571
 
 
572
        widget = GTK_WIDGET (gtk_builder_get_object (shell_installed->priv->builder,
 
573
                                                     "button_installed_counter"));
 
574
        pending = gs_plugin_loader_get_pending (plugin_loader);
 
575
        if (pending->len == 0) {
 
576
                gtk_widget_hide (widget);
 
577
        } else {
 
578
                gtk_widget_show (widget);
 
579
                label = g_strdup_printf ("%d", pending->len);
 
580
                gtk_label_set_label (GTK_LABEL (widget), label);
 
581
                g_free (label);
 
582
        }
 
583
        for (i = 0; i < pending->len; i++) {
 
584
                app = GS_APP (g_ptr_array_index (pending, i));
 
585
                /* Be careful not to add pending apps more than once. */
 
586
                if (gs_shell_installed_has_app (shell_installed, app) == FALSE)
 
587
                        gs_shell_installed_add_app (shell_installed, app);
 
588
        }
 
589
 
 
590
        g_ptr_array_unref (pending);
 
591
}
 
592
 
 
593
static void
 
594
set_selection_mode (GsShellInstalled *shell_installed, gboolean selection_mode)
 
595
{
 
596
        GsShellInstalledPrivate *priv = shell_installed->priv;
 
597
        GList *children, *l;
 
598
        GtkWidget *header;
 
599
        GtkWidget *widget;
 
600
        GtkStyleContext *context;
 
601
        
 
602
        if (priv->selection_mode == selection_mode)
 
603
                return;
 
604
 
 
605
        priv->selection_mode = selection_mode;
 
606
 
 
607
        header = GTK_WIDGET (gtk_builder_get_object (priv->builder, "header"));
 
608
        context = gtk_widget_get_style_context (header);
 
609
        if (priv->selection_mode) {
 
610
                gtk_header_bar_set_show_close_button (GTK_HEADER_BAR (header), FALSE);
 
611
                gtk_style_context_add_class (context, "selection-mode");
 
612
                widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "button_select"));
 
613
                gtk_button_set_image (GTK_BUTTON (widget), NULL);
 
614
                gtk_button_set_label (GTK_BUTTON (widget), _("_Cancel"));
 
615
                gtk_button_set_use_underline (GTK_BUTTON (widget), TRUE);
 
616
                gtk_widget_show (widget);
 
617
                widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "buttonbox_main"));
 
618
                gtk_widget_hide (widget);
 
619
                widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "header_selection_menu_button"));
 
620
                gtk_widget_show (widget);
 
621
                widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "header_selection_label"));
 
622
                gtk_label_set_label (GTK_LABEL (widget), _("Click on items to select them"));
 
623
        } else {
 
624
                gtk_header_bar_set_show_close_button (GTK_HEADER_BAR (header), TRUE);
 
625
                gtk_style_context_remove_class (context, "selection-mode");
 
626
                widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "button_select"));
 
627
                gtk_button_set_image (GTK_BUTTON (widget), gtk_image_new_from_icon_name ("object-select-symbolic", GTK_ICON_SIZE_MENU));
 
628
                gtk_button_set_label (GTK_BUTTON (widget), NULL);
 
629
                gtk_widget_show (widget);
 
630
                widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "buttonbox_main"));
 
631
                gtk_widget_show (widget);
 
632
                widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "header_selection_menu_button"));
 
633
                gtk_widget_hide (widget);
 
634
 
 
635
                gtk_widget_hide (priv->button_folder_add);
 
636
                gtk_widget_hide (priv->button_folder_move);
 
637
                gtk_widget_hide (priv->button_folder_remove);
 
638
        }
 
639
 
 
640
        children = gtk_container_get_children (GTK_CONTAINER (priv->list_box_install));
 
641
        for (l = children; l; l = l->next) {
 
642
                GsAppRow *app_row = GS_APP_ROW (l->data);
 
643
                gs_app_row_set_selectable (app_row,
 
644
                                           priv->selection_mode);
 
645
        }
 
646
        g_list_free (children);
 
647
 
 
648
        gtk_revealer_set_reveal_child (GTK_REVEALER (priv->bottom_install), priv->selection_mode);
 
649
}
 
650
 
 
651
static void
 
652
selection_mode_cb (GtkButton *button, GsShellInstalled *shell_installed)
 
653
{
 
654
        GsShellInstalledPrivate *priv = shell_installed->priv;
 
655
 
 
656
        set_selection_mode (shell_installed, !priv->selection_mode);
 
657
}
 
658
 
 
659
static GList *
 
660
get_selected_apps (GsShellInstalled *shell_installed)
 
661
{
 
662
        GsShellInstalledPrivate *priv = shell_installed->priv;
 
663
        GList *children, *l, *list;
 
664
 
 
665
        list = NULL;
 
666
        children = gtk_container_get_children (GTK_CONTAINER (priv->list_box_install));
 
667
        for (l = children; l; l = l->next) {
 
668
                GsAppRow *app_row = GS_APP_ROW (l->data);
 
669
                if (gs_app_row_get_selected (app_row)) {
 
670
                        list = g_list_prepend (list, gs_app_row_get_app (app_row));
 
671
                }
 
672
        }
 
673
        g_list_free (children);
 
674
 
 
675
        return list;
 
676
}
 
677
 
 
678
static void
 
679
selection_changed (GsShellInstalled *shell_installed)
 
680
{
 
681
        GsShellInstalledPrivate *priv = shell_installed->priv;
 
682
        GsFolders *folders;
 
683
        GList *apps, *l;
 
684
        GsApp *app;
 
685
        gboolean has_folders, has_nonfolders;
 
686
 
 
687
        folders = gs_folders_get ();
 
688
        has_folders = has_nonfolders = FALSE;
 
689
        apps = get_selected_apps (shell_installed);
 
690
        for (l = apps; l; l = l->next) {
 
691
                app = l->data;
 
692
                if (gs_folders_get_app_folder (folders,
 
693
                                               gs_app_get_id (app),
 
694
                                               gs_app_get_categories (app))) {
 
695
                        has_folders = TRUE;
 
696
                } else {
 
697
                        has_nonfolders = TRUE;
 
698
                }
 
699
        }
 
700
        g_list_free (apps);
 
701
        g_object_unref (folders);
 
702
 
 
703
        gtk_widget_set_visible (priv->button_folder_add, has_nonfolders);
 
704
        gtk_widget_set_visible (priv->button_folder_move, has_folders && !has_nonfolders);
 
705
        gtk_widget_set_visible (priv->button_folder_remove, has_folders);
 
706
}
 
707
 
 
708
static gboolean
 
709
folder_dialog_done (GsShellInstalled *shell_installed)
 
710
{
 
711
        set_selection_mode (shell_installed, FALSE);
 
712
        return FALSE;
 
713
}
 
714
 
 
715
static void
 
716
show_folder_dialog (GtkButton *button, GsShellInstalled *shell_installed)
 
717
{
 
718
        GtkWidget *toplevel;
 
719
        GtkWidget *dialog;
 
720
        GList *apps;
 
721
 
 
722
        toplevel = gtk_widget_get_toplevel (GTK_WIDGET (button));
 
723
        apps = get_selected_apps (shell_installed);
 
724
        dialog = gs_app_folder_dialog_new (GTK_WINDOW (toplevel), apps);
 
725
        g_list_free (apps);
 
726
        gtk_window_present (GTK_WINDOW (dialog));
 
727
        g_signal_connect_swapped (dialog, "delete-event",
 
728
                                  G_CALLBACK (folder_dialog_done), shell_installed);
 
729
}
 
730
 
 
731
static void
 
732
remove_folders (GtkButton *button, GsShellInstalled *shell_installed)
 
733
{
 
734
        GList *apps, *l;
 
735
        GsFolders *folders;
 
736
        GsApp *app;
 
737
 
 
738
        folders = gs_folders_get ();
 
739
        apps = get_selected_apps (shell_installed);
 
740
        for (l = apps; l; l = l->next) {
 
741
                app = l->data;
 
742
                gs_folders_set_app_folder (folders,
 
743
                                           gs_app_get_id (app),
 
744
                                           gs_app_get_categories (app),
 
745
                                           NULL);
 
746
        }
 
747
        g_list_free (apps);
 
748
 
 
749
        gs_folders_save (folders);
 
750
        g_object_unref (folders);
 
751
 
 
752
        set_selection_mode (shell_installed, FALSE);
 
753
}
 
754
 
 
755
static void
 
756
select_all_cb (GtkMenuItem *item, GsShellInstalled *shell_installed)
 
757
{
 
758
        GsShellInstalledPrivate *priv = shell_installed->priv;
 
759
        GList *children, *l;
 
760
 
 
761
        children = gtk_container_get_children (GTK_CONTAINER (priv->list_box_install));
 
762
        for (l = children; l; l = l->next) {
 
763
                GsAppRow *app_row = GS_APP_ROW (l->data);
 
764
                gs_app_row_set_selected (app_row, TRUE);
 
765
        }
 
766
        g_list_free (children);
 
767
}
 
768
 
 
769
static void
 
770
select_none_cb (GtkMenuItem *item, GsShellInstalled *shell_installed)
 
771
{
 
772
        GsShellInstalledPrivate *priv = shell_installed->priv;
 
773
        GList *children, *l;
 
774
 
 
775
        children = gtk_container_get_children (GTK_CONTAINER (priv->list_box_install));
 
776
        for (l = children; l; l = l->next) {
 
777
                GsAppRow *app_row = GS_APP_ROW (l->data);
 
778
                gs_app_row_set_selected (app_row, FALSE);
 
779
        }
 
780
        g_list_free (children);
 
781
}
 
782
 
 
783
/**
 
784
 * gs_shell_installed_setup:
 
785
 */
 
786
void
 
787
gs_shell_installed_setup (GsShellInstalled *shell_installed,
 
788
                          GsShell *shell,
 
789
                          GsPluginLoader *plugin_loader,
 
790
                          GtkBuilder *builder,
 
791
                          GCancellable *cancellable)
 
792
{
 
793
        GsShellInstalledPrivate *priv = shell_installed->priv;
 
794
        GtkWidget *widget;
 
795
 
 
796
        g_return_if_fail (GS_IS_SHELL_INSTALLED (shell_installed));
 
797
 
 
798
        priv->shell = shell;
 
799
        priv->plugin_loader = g_object_ref (plugin_loader);
 
800
        g_signal_connect (priv->plugin_loader, "pending-apps-changed",
 
801
                          G_CALLBACK (gs_shell_installed_pending_apps_changed_cb),
 
802
                          shell_installed);
 
803
 
 
804
        priv->builder = g_object_ref (builder);
 
805
        priv->cancellable = g_object_ref (cancellable);
 
806
 
 
807
        /* setup installed */
 
808
        g_signal_connect (priv->list_box_install, "row-activated",
 
809
                          G_CALLBACK (gs_shell_installed_app_row_activated_cb), shell_installed);
 
810
        gtk_list_box_set_header_func (GTK_LIST_BOX (priv->list_box_install),
 
811
                                      gs_shell_installed_list_header_func,
 
812
                                      shell_installed, NULL);
 
813
        gtk_list_box_set_sort_func (GTK_LIST_BOX (priv->list_box_install),
 
814
                                    gs_shell_installed_sort_func,
 
815
                                    shell_installed, NULL);
 
816
 
 
817
        g_signal_connect (priv->button_folder_add, "clicked",
 
818
                          G_CALLBACK (show_folder_dialog), shell_installed);
 
819
        
 
820
        g_signal_connect (priv->button_folder_move, "clicked",
 
821
                          G_CALLBACK (show_folder_dialog), shell_installed);
 
822
        
 
823
        g_signal_connect (priv->button_folder_remove, "clicked",
 
824
                          G_CALLBACK (remove_folders), shell_installed);
 
825
 
 
826
        widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "button_select"));
 
827
        g_signal_connect (widget, "clicked",
 
828
                          G_CALLBACK (selection_mode_cb), shell_installed);
 
829
 
 
830
        widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "button_select"));
 
831
        gtk_button_set_image (GTK_BUTTON (widget), gtk_image_new_from_icon_name ("object-select-symbolic", GTK_ICON_SIZE_MENU));
 
832
        gtk_button_set_label (GTK_BUTTON (widget), NULL);
 
833
 
 
834
        widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "select_all_menuitem"));
 
835
        g_signal_connect (widget, "activate",
 
836
                          G_CALLBACK (select_all_cb), shell_installed);
 
837
        widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "select_none_menuitem"));
 
838
        g_signal_connect (widget, "activate",
 
839
                          G_CALLBACK (select_none_cb), shell_installed);
 
840
}
 
841
 
 
842
/**
 
843
 * gs_shell_installed_class_init:
 
844
 **/
 
845
static void
 
846
gs_shell_installed_class_init (GsShellInstalledClass *klass)
 
847
{
 
848
        GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
849
        GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
 
850
 
 
851
        object_class->finalize = gs_shell_installed_finalize;
 
852
 
 
853
        gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Software/gs-shell-installed.ui");
 
854
 
 
855
        gtk_widget_class_bind_template_child_private (widget_class, GsShellInstalled, bottom_install);
 
856
        gtk_widget_class_bind_template_child_private (widget_class, GsShellInstalled, button_folder_add);
 
857
        gtk_widget_class_bind_template_child_private (widget_class, GsShellInstalled, button_folder_move);
 
858
        gtk_widget_class_bind_template_child_private (widget_class, GsShellInstalled, button_folder_remove);
 
859
        gtk_widget_class_bind_template_child_private (widget_class, GsShellInstalled, list_box_install);
 
860
        gtk_widget_class_bind_template_child_private (widget_class, GsShellInstalled, scrolledwindow_install);
 
861
        gtk_widget_class_bind_template_child_private (widget_class, GsShellInstalled, spinner_install);
 
862
        gtk_widget_class_bind_template_child_private (widget_class, GsShellInstalled, stack_install);
 
863
}
 
864
 
 
865
/**
 
866
 * gs_shell_installed_init:
 
867
 **/
 
868
static void
 
869
gs_shell_installed_init (GsShellInstalled *shell_installed)
 
870
{
 
871
        gtk_widget_init_template (GTK_WIDGET (shell_installed));
 
872
 
 
873
        shell_installed->priv = gs_shell_installed_get_instance_private (shell_installed);
 
874
        shell_installed->priv->sizegroup_image = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
 
875
        shell_installed->priv->sizegroup_name = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
 
876
}
 
877
 
 
878
/**
 
879
 * gs_shell_installed_finalize:
 
880
 **/
 
881
static void
 
882
gs_shell_installed_finalize (GObject *object)
 
883
{
 
884
        GsShellInstalled *shell_installed = GS_SHELL_INSTALLED (object);
 
885
        GsShellInstalledPrivate *priv = shell_installed->priv;
 
886
 
 
887
        g_object_unref (priv->sizegroup_image);
 
888
        g_object_unref (priv->sizegroup_name);
 
889
 
 
890
        g_object_unref (priv->builder);
 
891
        g_object_unref (priv->plugin_loader);
 
892
        g_object_unref (priv->cancellable);
 
893
 
 
894
        G_OBJECT_CLASS (gs_shell_installed_parent_class)->finalize (object);
 
895
}
 
896
 
 
897
/**
 
898
 * gs_shell_installed_new:
 
899
 **/
 
900
GsShellInstalled *
 
901
gs_shell_installed_new (void)
 
902
{
 
903
        GsShellInstalled *shell_installed;
 
904
        shell_installed = g_object_new (GS_TYPE_SHELL_INSTALLED, NULL);
 
905
        return GS_SHELL_INSTALLED (shell_installed);
 
906
}
 
907
 
 
908
/* vim: set noexpandtab: */