summaryrefslogtreecommitdiffstats
path: root/widgets/src/BaseWindow.c
blob: dd7d41fa632c185b4a387a46d57d59f87f6c6876 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
/*
 * Copyright (C) 2011-2012  Red Hat, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Author: Chris Lumens <clumens@redhat.com>
 */

#include <string.h>

#include "BaseWindow.h"
#include "intl.h"

/**
 * SECTION: BaseWindow
 * @title: AnacondaBaseWindow
 * @short_description: Top-level, non-resizeable window
 *
 * A #AnacondaBaseWindow is a top-level, non-resizeable window that contains
 * other widgets and serves as the base class from which all other specialized
 * Anaconda windows are derived.  It is undecorated.
 *
 * The window consists of two areas:
 *
 * - A navigation area in the top of the screen, consisting of some basic
 *   information about what is being displayed and what is being installed.
 *
 * - An action area in the majority of the screen.  This area is where
 *   subclasses should add their particular widgets.
 *
 * <refsect2 id="AnacondaBaseWindow-BUILDER-UI"><title>AnacondaBaseWindow as GtkBuildable</title>
 * <para>
 * The AnacondaBaseWindow implementation of the #GtkBuildable interface exposes
 * the @action_area as an internal child with the name "action_area".
 * </para>
 * <example>
 * <title>A <structname>AnacondaBaseWindow</structname> UI definition fragment.</title>
 * <programlisting><![CDATA[
 * <object class="AnacondaBaseWindow" id="window1">
 *     <child internal-child="action_area">
 *         <object class="GtkVBox" id="vbox1">
 *             <child>
 *                 <object class="GtkLabel" id="label1">
 *                     <property name="label" translatable="yes">THIS IS ONE LABEL</property>
 *                 </object>
 *             </child>
 *             <child>
 *                 <object class="GtkLabel" id="label2">
 *                     <property name="label" translatable="yes">THIS IS ANOTHER LABEL</property>
 *                 </object>
 *             </child>
 *         </object>
 *     </child>
 * </object>
 * ]]></programlisting>
 * </example>
 * </refsect2>
 */

enum {
    SIGNAL_INFO_BAR_CLICKED,
    LAST_SIGNAL
};

static guint window_signals[LAST_SIGNAL] = { 0 };

enum {
    PROP_DISTRIBUTION = 1,
    PROP_WINDOW_NAME
};

#define DEFAULT_DISTRIBUTION  N_("DISTRIBUTION INSTALLATION")
#define DEFAULT_WINDOW_NAME   N_("SPOKE NAME")
#define DEFAULT_BETA          N_("PRE-RELEASE / TESTING")

struct _AnacondaBaseWindowPrivate {
    gboolean    is_beta, info_shown;
    GtkWidget  *main_box, *event_box, *info_bar;
    GtkWidget  *alignment;
    GtkWidget  *nav_area, *action_area;
    GtkWidget  *name_label, *distro_label, *beta_label;

    /* Untranslated versions of various things. */
    gchar *orig_name, *orig_distro, *orig_beta;
};

static void anaconda_base_window_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
static void anaconda_base_window_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
static void anaconda_base_window_buildable_init(GtkBuildableIface *iface);

static gboolean anaconda_base_window_info_bar_clicked(GtkWidget *widget, GdkEvent *event, AnacondaBaseWindow *win);

G_DEFINE_TYPE_WITH_CODE(AnacondaBaseWindow, anaconda_base_window, GTK_TYPE_WINDOW,
                        G_IMPLEMENT_INTERFACE(GTK_TYPE_BUILDABLE, anaconda_base_window_buildable_init))

static void anaconda_base_window_class_init(AnacondaBaseWindowClass *klass) {
    GObjectClass *object_class = G_OBJECT_CLASS(klass);

    object_class->set_property = anaconda_base_window_set_property;
    object_class->get_property = anaconda_base_window_get_property;

    /**
     * AnacondaBaseWindow:distribution:
     *
     * The :distribution string is displayed in the upper right corner of all
     * windows throughout installation.
     *
     * Since: 1.0
     */
    g_object_class_install_property(object_class,
                                    PROP_DISTRIBUTION,
                                    g_param_spec_string("distribution",
                                                        P_("Distribution"),
                                                        P_("The distribution being installed"),
                                                        DEFAULT_DISTRIBUTION,
                                                        G_PARAM_READWRITE));

    /**
     * AnacondaBaseWindow:window-name:
     *
     * The name of the currently displayed window, displayed in the upper
     * left corner of all windows with a title throughout installation.
     *
     * Since: 1.0
     */
    g_object_class_install_property(object_class,
                                    PROP_WINDOW_NAME,
                                    g_param_spec_string("window-name",
                                                        P_("Window Name"),
                                                        P_("The name of this spoke"),
                                                        DEFAULT_WINDOW_NAME,
                                                        G_PARAM_READWRITE));

    klass->info_bar_clicked = NULL;

    /**
     * AnacondaBaseWindow::info-bar-clicked:
     * @window: the window that received the signal
     *
     * Emitted when a visible info bar at the bottom of the window has been clicked
     * (pressed and released).
     *
     * Since: 1.0
     */
    window_signals[SIGNAL_INFO_BAR_CLICKED] = g_signal_new("info-bar-clicked",
                                                           G_TYPE_FROM_CLASS(object_class),
                                                           G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
                                                           G_STRUCT_OFFSET(AnacondaBaseWindowClass, info_bar_clicked),
                                                           NULL, NULL,
                                                           g_cclosure_marshal_VOID__VOID,
                                                           G_TYPE_NONE, 0);

    g_type_class_add_private(object_class, sizeof(AnacondaBaseWindowPrivate));
}

/**
 * anaconda_base_window_new:
 *
 * Creates a new #AnacondaBaseWindow, which is a toplevel, non-resizeable
 * window that contains other widgets.  This is the base class for all other
 * Anaconda windows and creates the window style that all windows will share.
 *
 * Returns: A new #AnacondaBaseWindow.
 */
GtkWidget *anaconda_base_window_new() {
    return g_object_new(ANACONDA_TYPE_BASE_WINDOW, NULL);
}

static void anaconda_base_window_init(AnacondaBaseWindow *win) {
    char *markup;

    win->priv = G_TYPE_INSTANCE_GET_PRIVATE(win,
                                            ANACONDA_TYPE_BASE_WINDOW,
                                            AnacondaBaseWindowPrivate);

    win->priv->is_beta = FALSE;
    win->priv->info_shown = FALSE;

    win->priv->orig_name = NULL;
    win->priv->orig_distro = NULL;
    win->priv->orig_beta = NULL;

    /* Set properties on the parent (Gtk.Window) class. */
    gtk_window_set_decorated(GTK_WINDOW(win), FALSE);
    gtk_window_maximize(GTK_WINDOW(win));
    g_object_set(win, "expand", TRUE, NULL);
    gtk_container_set_border_width(GTK_CONTAINER(win), 0);

    /* First, construct a top-level box that everything will go in.  Remember
     * a Window can only hold one widget, and we may very well need to add
     * more things later.
     */
    win->priv->main_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
    gtk_container_add(GTK_CONTAINER(win), win->priv->main_box);

    /* Then the navigation area that sits as the first item in the main box
     * for every Window class.
     */
    win->priv->nav_area = gtk_grid_new();
    gtk_grid_set_row_homogeneous(GTK_GRID(win->priv->nav_area), FALSE);
    gtk_grid_set_column_homogeneous(GTK_GRID(win->priv->nav_area), FALSE);
    gtk_widget_set_margin_left(win->priv->nav_area, 6);
    gtk_widget_set_margin_right(win->priv->nav_area, 6);
    gtk_widget_set_margin_top(win->priv->nav_area, 6);
    gtk_box_pack_start(GTK_BOX(win->priv->main_box), win->priv->nav_area, FALSE, FALSE, 0);

    /* Second in the main box is an alignment, because we want to be able
     * to control the amount of space the Window's content takes up on the
     * screen.
     */
    win->priv->alignment = gtk_alignment_new(0.5, 0.0, 0.0, 0.5);
    gtk_box_pack_start(GTK_BOX(win->priv->main_box), win->priv->alignment, TRUE, TRUE, 0);

    /* The action_area goes inside the alignment and represents the main
     * place for content to go.
     */
    win->priv->action_area = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
    gtk_container_add(GTK_CONTAINER(win->priv->alignment), win->priv->action_area);

    /* And now we can finally create the widgets that go in all those layout
     * pieces up above.
     */

    /* Create the name label. */
    win->priv->name_label = gtk_label_new(NULL);
    markup = g_markup_printf_escaped("<span weight='bold' size='large'>%s</span>", _(DEFAULT_WINDOW_NAME));
    gtk_label_set_markup(GTK_LABEL(win->priv->name_label), markup);
    g_free(markup);
    gtk_misc_set_alignment(GTK_MISC(win->priv->name_label), 0, 0);
    gtk_widget_set_hexpand(win->priv->name_label, TRUE);

    win->priv->orig_name = g_strdup(DEFAULT_WINDOW_NAME);

    /* Create the distribution label. */
    win->priv->distro_label = gtk_label_new(NULL);
    markup = g_markup_printf_escaped("<span size='large'>%s</span>", _(DEFAULT_DISTRIBUTION));
    gtk_label_set_markup(GTK_LABEL(win->priv->distro_label), markup);
    g_free(markup);
    gtk_misc_set_alignment(GTK_MISC(win->priv->distro_label), 0, 0);

    win->priv->orig_distro = g_strdup(DEFAULT_DISTRIBUTION);

    /* Create the betanag label. */
    win->priv->beta_label = gtk_label_new(NULL);
    markup = g_markup_printf_escaped("<span foreground='red' weight='bold' size='large'>%s</span>", _(DEFAULT_BETA));
    gtk_label_set_markup(GTK_LABEL(win->priv->beta_label), markup);
    g_free(markup);
    gtk_misc_set_alignment(GTK_MISC(win->priv->beta_label), 0, 0);
    gtk_widget_set_no_show_all(win->priv->beta_label, TRUE);

    win->priv->orig_beta = g_strdup(DEFAULT_BETA);

    /* Add everything to the nav area. */
    gtk_grid_attach(GTK_GRID(win->priv->nav_area), win->priv->name_label, 0, 0, 1, 1);
    gtk_grid_attach(GTK_GRID(win->priv->nav_area), win->priv->distro_label, 1, 0, 1, 1);
    gtk_grid_attach(GTK_GRID(win->priv->nav_area), win->priv->beta_label, 1, 1, 1, 1);
}

static void anaconda_base_window_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) {
    AnacondaBaseWindow *widget = ANACONDA_BASE_WINDOW(object);
    AnacondaBaseWindowPrivate *priv = widget->priv;

    switch(prop_id) {
        case PROP_DISTRIBUTION:
            g_value_set_string(value, gtk_label_get_text(GTK_LABEL(priv->distro_label)));
            break;

        case PROP_WINDOW_NAME:
            g_value_set_string(value, gtk_label_get_text(GTK_LABEL(priv->name_label)));
            break;
    }
}

static void anaconda_base_window_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) {
    AnacondaBaseWindow *widget = ANACONDA_BASE_WINDOW(object);
    AnacondaBaseWindowPrivate *priv = widget->priv;

    switch(prop_id) {
        case PROP_DISTRIBUTION: {
            char *markup = g_markup_printf_escaped("<span size='large'>%s</span>", g_value_get_string(value));
            gtk_label_set_markup(GTK_LABEL(priv->distro_label), markup);
            g_free(markup);

            if (priv->orig_distro)
                g_free(priv->orig_distro);
            priv->orig_distro = g_strdup(g_value_get_string(value));
            break;
        }

        case PROP_WINDOW_NAME: {
            char *markup = g_markup_printf_escaped("<span weight='bold' size='large'>%s</span>", g_value_get_string(value));
            gtk_label_set_markup(GTK_LABEL(priv->name_label), markup);
            g_free(markup);

            if (priv->orig_name)
                g_free(priv->orig_name);
            priv->orig_name = g_strdup(g_value_get_string(value));
            break;
        }
    }
}

/**
 * anaconda_base_window_get_beta:
 * @win: a #AnacondaBaseWindow
 *
 * Returns whether or not this window is set to display the betanag warning.
 *
 * Returns: Whether @win is set to display the betanag warning
 *
 * Since: 1.0
 */
gboolean anaconda_base_window_get_beta(AnacondaBaseWindow *win) {
    return win->priv->is_beta;
}

/**
 * anaconda_base_window_set_beta:
 * @win: a #AnacondaBaseWindow
 * @is_beta: %TRUE to display the betanag warning
 *
 * Sets up the window to display the betanag warning in red along the top of
 * the screen.
 *
 * Since: 1.0
 */
void anaconda_base_window_set_beta(AnacondaBaseWindow *win, gboolean is_beta) {
    win->priv->is_beta = is_beta;

    if (is_beta)
        gtk_widget_show(win->priv->beta_label);
    else
        gtk_widget_hide(win->priv->beta_label);
}

/**
 * anaconda_base_window_get_action_area:
 * @win: a #AnacondaBaseWindow
 *
 * Returns the action area of @win.
 *
 * Returns: (transfer none): The action area
 *
 * Since: 1.0
 */
GtkWidget *anaconda_base_window_get_action_area(AnacondaBaseWindow *win) {
    return win->priv->action_area;
}

/**
 * anaconda_base_window_get_nav_area:
 * @win: a #AnacondaBaseWindow
 *
 * Returns the navigation area of @win.
 *
 * Returns: (transfer none): The navigation area
 *
 * Since: 1.0
 */
GtkWidget *anaconda_base_window_get_nav_area(AnacondaBaseWindow *win) {
    return win->priv->nav_area;
}

/**
 * anaconda_base_window_get_main_box:
 * @win: a #AnacondaBaseWindow
 *
 * Returns the main content area of @win.
 *
 * Returns: (transfer none): The main content area
 *
 * Since: 1.0
 */
GtkWidget *anaconda_base_window_get_main_box(AnacondaBaseWindow *win) {
    return win->priv->main_box;
}

/**
 * anaconda_base_window_get_alignment:
 * @win: a #AnacondaBaseWindow
 *
 * Returns the internal alignment widget of @win.
 *
 * Returns: (transfer none): The alignment widget
 *
 * Since: 1.0
 */
GtkWidget *anaconda_base_window_get_alignment(AnacondaBaseWindow *win) {
    return win->priv->alignment;
}

/**
 * anaconda_base_window_set_info:
 * @win: a #AnacondaBaseWindow
 * @ty: a #GtkMessageType
 * @msg: a message
 *
 * Causes an info bar to be shown at the bottom of the screen with the provided
 * message.  The type argument is used to determine the background color of the
 * info bar area.  Only one message may be shown at a time.  In order to show
 * a second message, anaconda_base_window_clear_info must first be called.
 *
 * Since: 1.0
 */
void anaconda_base_window_set_info(AnacondaBaseWindow *win, GtkMessageType ty, const char *msg) {
    GtkWidget *label, *image, *content_area;

    if (win->priv->info_shown)
        return;

    label = gtk_label_new(_(msg));
    gtk_widget_show(label);

    win->priv->info_bar = gtk_info_bar_new();
    gtk_widget_set_no_show_all(win->priv->info_bar, TRUE);

    /* Wrap the info bar in an event box so clicking on it will do something. */
    win->priv->event_box = gtk_event_box_new();
    gtk_container_add(GTK_CONTAINER(win->priv->event_box), win->priv->info_bar);

    gtk_box_pack_end(GTK_BOX(win->priv->main_box), win->priv->event_box, FALSE, FALSE, 0);

    /* Hook up the signal handler for the info bar.  It will just raise our own
     * custom signal for the whole window.  It will be disconnected when the info
     * bar is hidden.
     */
    gtk_widget_add_events(GTK_WIDGET(win->priv->event_box), GDK_BUTTON_RELEASE_MASK);
    g_signal_connect(win->priv->event_box, "button-release-event",
                     G_CALLBACK(anaconda_base_window_info_bar_clicked), win);

    content_area = gtk_info_bar_get_content_area(GTK_INFO_BAR(win->priv->info_bar));

    image = gtk_image_new_from_stock(GTK_STOCK_DIALOG_WARNING, GTK_ICON_SIZE_MENU);
    gtk_widget_show(image);
    gtk_container_add(GTK_CONTAINER(content_area), image);

    gtk_container_add(GTK_CONTAINER(content_area), label);
    gtk_info_bar_set_message_type(GTK_INFO_BAR(win->priv->info_bar), ty);
    gtk_widget_show(win->priv->info_bar);

    win->priv->info_shown = TRUE;
}

static gboolean anaconda_base_window_info_bar_clicked(GtkWidget *wiget, GdkEvent *event, AnacondaBaseWindow *win) {
    g_signal_emit(win, window_signals[SIGNAL_INFO_BAR_CLICKED], 0);
    return FALSE;
}

/**
 * anaconda_base_window_clear_info:
 * @win: a #AnacondaBaseWindow
 *
 * Clear and hide any info bar being shown at the bottom of the screen.  This
 * must be called before a second call to anaconda_base_window_set_info takes
 * effect.
 *
 * Since: 1.0
 */
void anaconda_base_window_clear_info(AnacondaBaseWindow *win) {
    if (!win->priv->info_shown)
        return;

    g_object_disconnect(win->priv->info_bar, "button-release-event", NULL);

    gtk_widget_hide(win->priv->info_bar);
    gtk_widget_destroy(win->priv->info_bar);
    gtk_widget_destroy(win->priv->event_box);
    win->priv->info_shown = FALSE;
}

/**
 * anaconda_base_window_retranslate
 * @win: a #AnacondaBaseWindow
 *
 * Reload translations for this widget as needed.  Generally, this is not
 * needed.  However when changing the language during installation, we need
 * to be able to make sure the screen gets retranslated.  This function is
 * kind of ugly but avoids having to destroy and reload the screen.
 *
 * Since: 1.0
 */
void anaconda_base_window_retranslate(AnacondaBaseWindow *win) {
    char *markup;
    GValue distro = G_VALUE_INIT;

    g_value_init(&distro, G_TYPE_STRING);
    g_value_set_string(&distro, _(win->priv->orig_distro));

    anaconda_base_window_set_property((GObject *) win, PROP_DISTRIBUTION, &distro, NULL);

    /* A window name is not necessarily set. */
    if (strcmp(gtk_label_get_text(GTK_LABEL(win->priv->name_label)), "") != 0) {
        GValue name = G_VALUE_INIT;

        g_value_init(&name, G_TYPE_STRING);
        g_value_set_string(&name, _(win->priv->orig_name));

        anaconda_base_window_set_property((GObject *) win, PROP_WINDOW_NAME, &name, NULL);
    }

    markup = g_markup_printf_escaped("<span foreground='red' weight='bold' size='large'>%s</span>",
                                     _(win->priv->orig_beta));
    gtk_label_set_markup(GTK_LABEL(win->priv->beta_label), markup);
    g_free(markup);
}

static GtkBuildableIface *parent_buildable_iface;

static void
anaconda_base_window_buildable_add_child (GtkBuildable *window,
                                          GtkBuilder *builder,
                                          GObject *child,
                                          const gchar *type) {
    gtk_container_add(GTK_CONTAINER(anaconda_base_window_get_action_area(ANACONDA_BASE_WINDOW(window))),
                      GTK_WIDGET(child));
}

static GObject *
anaconda_base_window_buildable_get_internal_child (GtkBuildable *buildable,
                                                   GtkBuilder *builder,
                                                   const gchar *childname) {
    if (!strcmp(childname, "main_box"))
        return G_OBJECT(anaconda_base_window_get_main_box(ANACONDA_BASE_WINDOW(buildable)));
    else if (!strcmp(childname, "nav_area"))
        return G_OBJECT(ANACONDA_BASE_WINDOW(buildable)->priv->nav_area);
    else if (!strcmp(childname, "alignment"))
        return G_OBJECT(ANACONDA_BASE_WINDOW(buildable)->priv->alignment);
    else if (!strcmp(childname, "action_area"))
        return G_OBJECT(anaconda_base_window_get_action_area(ANACONDA_BASE_WINDOW(buildable)));

    return parent_buildable_iface->get_internal_child (buildable, builder, childname);
}

static void anaconda_base_window_buildable_init (GtkBuildableIface *iface) {
    parent_buildable_iface = g_type_interface_peek_parent (iface);
    iface->add_child = anaconda_base_window_buildable_add_child;
    iface->get_internal_child = anaconda_base_window_buildable_get_internal_child;
}