cc-display-panel.c 40.6 KB
Newer Older
1
/*
Bastien Nocera's avatar
Bastien Nocera committed
2
 * Copyright (C) 2007, 2008  Red Hat, Inc.
3
 * Copyright (C) 2013 Intel, Inc.
4
5
6
7
8
9
10
11
12
13
14
15
16
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
17
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18
19
20
21
22
 *
 */

#include "cc-display-panel.h"

Bastien Nocera's avatar
Bastien Nocera committed
23
24
#include <gtk/gtk.h>
#include <glib/gi18n.h>
25
#include <stdlib.h>
Bastien Nocera's avatar
Bastien Nocera committed
26
#include <gdesktop-enums.h>
27
#include <math.h>
28

29
30
31
#define HANDY_USE_UNSTABLE_API 1
#include <handy.h>

32
#include "shell/cc-object-storage.h"
33
#include "list-box-helper.h"
34
#include <libupower-glib/upower.h>
35

36
#include "cc-display-config-manager-dbus.h"
37
#include "cc-display-config.h"
38
#include "cc-display-arrangement.h"
39
#include "cc-night-light-page.h"
40
#include "cc-display-resources.h"
41
#include "cc-display-settings.h"
42

43
44
45
46
/* The minimum supported size for the panel
 * Note that WIDTH is assumed to be the larger size and we accept portrait
 * mode too effectively (in principle we should probably restrict the rotation
 * setting in that case). */
47
48
49
#define MINIMUM_WIDTH 740
#define MINIMUM_HEIGHT 530

Rui Matos's avatar
Rui Matos committed
50
51
52
#define PANEL_PADDING   32
#define SECTION_PADDING 32
#define HEADING_PADDING 12
53

54
typedef enum {
55
56
57
58
59
  CC_DISPLAY_CONFIG_SINGLE,
  CC_DISPLAY_CONFIG_JOIN,
  CC_DISPLAY_CONFIG_CLONE,

  CC_DISPLAY_CONFIG_INVALID_NONE,
60
} CcDisplayConfigType;
61

62
63
#define CC_DISPLAY_CONFIG_LAST_VALID CC_DISPLAY_CONFIG_CLONE

64
struct _CcDisplayPanel
65
{
66
67
  CcPanel parent_instance;

68
69
70
  CcDisplayConfigManager *manager;
  CcDisplayConfig *current_config;
  CcDisplayMonitor *current_output;
Bastien Nocera's avatar
Bastien Nocera committed
71

72
  gint                  rebuilding_counter;
73

74
  CcDisplayArrangement *arrangement;
75
  CcDisplaySettings    *settings;
76

77
  guint           focus_id;
Bastien Nocera's avatar
Bastien Nocera committed
78

79
80
  CcNightLightPage *night_light_page;
  GtkDialog *night_light_dialog;
Bastien Nocera's avatar
Bastien Nocera committed
81

82
83
  UpClient *up_client;
  gboolean lid_is_closed;
84
85

  GDBusProxy *shell_proxy;
86
87
88
89

  guint       sensor_watch_id;
  GDBusProxy *iio_sensor_proxy;
  gboolean    has_accelerometer;
Rui Matos's avatar
Rui Matos committed
90

91
  gchar     *main_title;
Rui Matos's avatar
Rui Matos committed
92
93
  GtkWidget *main_titlebar;
  GtkWidget *apply_titlebar;
94
95
  GtkWidget *apply_titlebar_apply;
  GtkWidget *apply_titlebar_warning;
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113

  GListStore     *primary_display_list;
  GtkListStore   *output_selection_list;

  GtkWidget      *arrangement_frame;
  GtkAlignment   *arrangement_bin;
  GtkRadioButton *config_type_join;
  GtkRadioButton *config_type_mirror;
  GtkRadioButton *config_type_single;
  GtkWidget      *config_type_switcher_frame;
  GtkLabel       *current_output_label;
  GtkWidget      *display_settings_frame;
  GtkSwitch      *output_enabled_switch;
  GtkComboBox    *output_selection_combo;
  GtkStack       *output_selection_stack;
  GtkButtonBox   *output_selection_two_first;
  GtkButtonBox   *output_selection_two_second;
  HdyComboRow    *primary_display_row;
114
  GtkWidget      *stack_switcher;
115
116
};

117
118
CC_PANEL_REGISTER (CcDisplayPanel, cc_display_panel)

Rui Matos's avatar
Rui Matos committed
119
120
121
122
static void
update_apply_button (CcDisplayPanel *panel);
static void
apply_current_configuration (CcDisplayPanel *self);
123
124
static void
reset_current_config (CcDisplayPanel *panel);
125
126
127
128
129
130
131
132
133
static void
rebuild_ui (CcDisplayPanel *panel);
static void
set_current_output (CcDisplayPanel   *panel,
                    CcDisplayMonitor *output,
                    gboolean          force);


static CcDisplayConfigType
134
config_get_current_type (CcDisplayPanel *panel)
135
{
136
  guint n_active_outputs;
137
138
139
140
141
142
143
144
145
146
147
148
  GList *outputs, *l;

  outputs = cc_display_config_get_ui_sorted_monitors (panel->current_config);
  n_active_outputs = 0;
  for (l = outputs; l; l = l->next)
    {
      CcDisplayMonitor *output = l->data;

      if (cc_display_monitor_is_useful (output))
        n_active_outputs += 1;
    }

149
150
  if (n_active_outputs == 0)
    return CC_DISPLAY_CONFIG_INVALID_NONE;
151

152
153
  if (n_active_outputs == 1)
    return CC_DISPLAY_CONFIG_SINGLE;
154

155
  if (cc_display_config_is_cloning (panel->current_config))
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
    return CC_DISPLAY_CONFIG_CLONE;

  return CC_DISPLAY_CONFIG_JOIN;
}

static CcDisplayConfigType
cc_panel_get_selected_type (CcDisplayPanel *panel)
{
  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (panel->config_type_join)))
    return CC_DISPLAY_CONFIG_JOIN;
  else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (panel->config_type_mirror)))
    return CC_DISPLAY_CONFIG_CLONE;
  else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (panel->config_type_single)))
    return CC_DISPLAY_CONFIG_SINGLE;
  else
    g_assert_not_reached ();
}

static void
config_ensure_of_type (CcDisplayPanel *panel, CcDisplayConfigType type)
{
177
  CcDisplayConfigType current_type = config_get_current_type (panel);
178
179
  GList *outputs, *l;

180
181
182
  /* Do not do anything if the current detected configuration type is
   * identitcal to what we expect. */
  if (type == current_type)
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
    return;

  reset_current_config (panel);

  outputs = cc_display_config_get_ui_sorted_monitors (panel->current_config);

  switch (type)
    {
    case CC_DISPLAY_CONFIG_SINGLE:
      g_debug ("Creating new single config");
      /* Disable all but the current primary output */
      cc_display_config_set_cloning (panel->current_config, FALSE);
      for (l = outputs; l; l = l->next)
        {
          CcDisplayMonitor *output = l->data;

          /* Select the current primary output as the active one */
          if (cc_display_monitor_is_primary (output))
            {
              cc_display_monitor_set_active (output, TRUE);
203
              cc_display_monitor_set_mode (output, cc_display_monitor_get_preferred_mode (output));
204
205
206
207
208
              set_current_output (panel, output, FALSE);
            }
          else
            {
              cc_display_monitor_set_active (output, FALSE);
209
              cc_display_monitor_set_mode (output, cc_display_monitor_get_preferred_mode (output));
210
211
212
213
214
215
            }
        }
      break;

    case CC_DISPLAY_CONFIG_JOIN:
      g_debug ("Creating new join config");
216
217
218
      /* Enable all usable outputs
       * Note that this might result in invalid configurations as we
       * we might not be able to drive all attached monitors. */
219
220
221
222
223
224
      cc_display_config_set_cloning (panel->current_config, FALSE);
      for (l = outputs; l; l = l->next)
        {
          CcDisplayMonitor *output = l->data;

          cc_display_monitor_set_active (output, cc_display_monitor_is_usable (output));
225
          cc_display_monitor_set_mode (output, cc_display_monitor_get_preferred_mode (output));
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
        }
      break;

    case CC_DISPLAY_CONFIG_CLONE:
      {
        g_debug ("Creating new clone config");
        GList *modes = cc_display_config_get_cloning_modes (panel->current_config);
        gint bw, bh;
        CcDisplayMode *best = NULL;

        /* Turn on cloning and select the best mode we can find by default */
        cc_display_config_set_cloning (panel->current_config, TRUE);

        while (modes)
          {
            CcDisplayMode *mode = modes->data;
            gint w, h;

            cc_display_mode_get_resolution (mode, &w, &h);
            if (best == NULL || (bw*bh < w*h))
              {
                best = mode;
                cc_display_mode_get_resolution (best, &bw, &bh);
              }

            modes = modes->next;
          }
        cc_display_config_set_mode_on_all_outputs (panel->current_config, best);
      }
      break;

    default:
      g_assert_not_reached ();
    }

  rebuild_ui (panel);
}
Rui Matos's avatar
Rui Matos committed
263

264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
static void
cc_panel_set_selected_type (CcDisplayPanel *panel, CcDisplayConfigType type)
{
  switch (type)
    {
    case CC_DISPLAY_CONFIG_JOIN:
      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (panel->config_type_join), TRUE);
      break;
    case CC_DISPLAY_CONFIG_CLONE:
      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (panel->config_type_mirror), TRUE);
      break;
    case CC_DISPLAY_CONFIG_SINGLE:
      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (panel->config_type_single), TRUE);
      break;
    default:
      g_assert_not_reached ();
    }

  config_ensure_of_type (panel, type);
}

Rui Matos's avatar
Rui Matos committed
285
286
287
static void
monitor_labeler_hide (CcDisplayPanel *self)
{
288
  if (!self->shell_proxy)
Rui Matos's avatar
Rui Matos committed
289
290
    return;

291
  g_dbus_proxy_call (self->shell_proxy,
Rui Matos's avatar
Rui Matos committed
292
293
294
                     "HideMonitorLabels",
                     NULL, G_DBUS_CALL_FLAGS_NONE,
                     -1, NULL, NULL, NULL);
295
296
}

297
298
299
static void
monitor_labeler_show (CcDisplayPanel *self)
{
300
  GList *outputs, *l;
301
  GVariantBuilder builder;
Rui Matos's avatar
Rui Matos committed
302
  gint number = 0;
303

304
  if (!self->shell_proxy || !self->current_config)
305
306
    return;

307
  outputs = cc_display_config_get_ui_sorted_monitors (self->current_config);
308
309
310
  if (!outputs)
    return;

311
  if (cc_display_config_is_cloning (self->current_config))
Rui Matos's avatar
Rui Matos committed
312
    return monitor_labeler_hide (self);
313

Rui Matos's avatar
Rui Matos committed
314
315
316
  g_variant_builder_init (&builder, G_VARIANT_TYPE_TUPLE);
  g_variant_builder_open (&builder, G_VARIANT_TYPE_ARRAY);

317
  for (l = outputs; l != NULL; l = l->next)
318
    {
319
320
      CcDisplayMonitor *output = l->data;

321
      number = cc_display_monitor_get_ui_number (output);
322
323
324
      if (number == 0)
        continue;

325
326
327
      g_variant_builder_add (&builder, "{sv}",
                             cc_display_monitor_get_connector_name (output),
                             g_variant_new_int32 (number));
328
329
330
331
    }

  g_variant_builder_close (&builder);

Rui Matos's avatar
Rui Matos committed
332
333
334
  if (number < 2)
    return monitor_labeler_hide (self);

335
  g_dbus_proxy_call (self->shell_proxy,
336
                     "ShowMonitorLabels",
337
338
339
340
341
342
343
344
                     g_variant_builder_end (&builder),
                     G_DBUS_CALL_FLAGS_NONE,
                     -1, NULL, NULL, NULL);
}

static void
ensure_monitor_labels (CcDisplayPanel *self)
{
345
  g_autoptr(GList) windows = NULL;
346
  GList *w;
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362

  windows = gtk_window_list_toplevels ();

  for (w = windows; w; w = w->next)
    {
      if (gtk_window_has_toplevel_focus (GTK_WINDOW (w->data)))
        {
          monitor_labeler_show (self);
          break;
        }
    }

  if (!w)
    monitor_labeler_hide (self);
}

Rui Matos's avatar
Rui Matos committed
363
364
365
366
367
368
369
370
371
372
373
static void
dialog_toplevel_focus_changed (CcDisplayPanel *self)
{
  ensure_monitor_labels (self);
}

static void
reset_titlebar (CcDisplayPanel *self)
{
  GtkWidget *toplevel = cc_shell_get_toplevel (cc_panel_get_shell (CC_PANEL (self)));

374
  if (self->main_titlebar)
Rui Matos's avatar
Rui Matos committed
375
    {
376
377
      gtk_window_set_titlebar (GTK_WINDOW (toplevel), self->main_titlebar);
      g_clear_object (&self->main_titlebar);
378
379
380
381

      /* The split header bar will not reset the window title, so do that here. */
      gtk_window_set_title (GTK_WINDOW (toplevel), self->main_title);
      g_clear_pointer (&self->main_title, g_free);
Rui Matos's avatar
Rui Matos committed
382
383
    }

384
385
386
  g_clear_object (&self->apply_titlebar);
  g_clear_object (&self->apply_titlebar_apply);
  g_clear_object (&self->apply_titlebar_warning);
Rui Matos's avatar
Rui Matos committed
387
388
389
390
391
392
393
}

static void
active_panel_changed (CcShell    *shell,
                      GParamSpec *pspec,
                      CcPanel    *self)
{
394
  g_autoptr(CcPanel) panel = NULL;
Rui Matos's avatar
Rui Matos committed
395
396
397
398
399
400

  g_object_get (shell, "active-panel", &panel, NULL);
  if (panel != self)
    reset_titlebar (CC_DISPLAY_PANEL (self));
}

401
402
403
static void
cc_display_panel_dispose (GObject *object)
{
404
  CcDisplayPanel *self = CC_DISPLAY_PANEL (object);
405
406
  CcShell *shell;
  GtkWidget *toplevel;
Bastien Nocera's avatar
Bastien Nocera committed
407

Rui Matos's avatar
Rui Matos committed
408
409
  reset_titlebar (CC_DISPLAY_PANEL (object));

410
  if (self->sensor_watch_id > 0)
411
    {
412
413
      g_bus_unwatch_name (self->sensor_watch_id);
      self->sensor_watch_id = 0;
414
415
    }

416
  g_clear_object (&self->iio_sensor_proxy);
417

418
  if (self->focus_id)
419
    {
420
      shell = cc_panel_get_shell (CC_PANEL (object));
421
422
423
      toplevel = cc_shell_get_toplevel (shell);
      if (toplevel != NULL)
        g_signal_handler_disconnect (G_OBJECT (toplevel),
424
425
                                     self->focus_id);
      self->focus_id = 0;
426
      monitor_labeler_hide (CC_DISPLAY_PANEL (object));
427
    }
428

429
430
431
  g_clear_object (&self->manager);
  g_clear_object (&self->current_config);
  g_clear_object (&self->up_client);
Bastien Nocera's avatar
Bastien Nocera committed
432

433
  g_clear_object (&self->shell_proxy);
434

435
  g_clear_pointer ((GtkWidget **) &self->night_light_dialog, gtk_widget_destroy);
436

437
  G_OBJECT_CLASS (cc_display_panel_parent_class)->dispose (object);
438
439
}

440
441
442
443
444
445
static void
on_arrangement_selected_ouptut_changed_cb (CcDisplayPanel *panel)
{
  set_current_output (panel, cc_display_arrangement_get_selected_output (panel->arrangement), FALSE);
}

446
447
448
449
450
451
452
453
454
455
static void
on_monitor_settings_updated_cb (CcDisplayPanel    *panel,
                                CcDisplayMonitor  *monitor,
                                CcDisplaySettings *settings)
{
  if (monitor)
    cc_display_config_snap_output (panel->current_config, monitor);
  update_apply_button (panel);
}

Rui Matos's avatar
Rui Matos committed
456
static void
457
458
on_config_type_toggled_cb (CcDisplayPanel *panel,
                           GtkRadioButton *btn)
459
{
460
  CcDisplayConfigType type;
461

462
  if (panel->rebuilding_counter > 0)
463
    return;
464

465
466
  if (!panel->current_config)
    return;
467

468
469
  if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (btn)))
    return;
Rui Matos's avatar
Rui Matos committed
470

471
472
  type = cc_panel_get_selected_type (panel);
  config_ensure_of_type (panel, type);
Bastien Nocera's avatar
Bastien Nocera committed
473
474
}

Rui Matos's avatar
Rui Matos committed
475
static void
476
on_night_light_list_box_row_activated_cb (CcDisplayPanel *panel)
Bastien Nocera's avatar
Bastien Nocera committed
477
{
478
479
  GtkWindow *toplevel;
  toplevel = GTK_WINDOW (cc_shell_get_toplevel (cc_panel_get_shell (CC_PANEL (panel))));
480
481
482
483
484
485
486
487
488
489
490
491
492

  if (!panel->night_light_dialog)
    {
      GtkWidget *content_area;

      panel->night_light_dialog = (GtkDialog *)gtk_dialog_new ();

      content_area = gtk_dialog_get_content_area (panel->night_light_dialog);
      gtk_container_add (GTK_CONTAINER (content_area),
                         GTK_WIDGET (panel->night_light_page));
      gtk_widget_show (GTK_WIDGET (panel->night_light_page));
    }

493
494
  gtk_window_set_transient_for (GTK_WINDOW (panel->night_light_dialog), toplevel);
  gtk_window_present (GTK_WINDOW (panel->night_light_dialog));
Rui Matos's avatar
Rui Matos committed
495
}
Bastien Nocera's avatar
Bastien Nocera committed
496

497
498
static void
on_output_enabled_active_changed_cb (CcDisplayPanel *panel)
Rui Matos's avatar
Rui Matos committed
499
{
500
  gboolean active;
Bastien Nocera's avatar
Bastien Nocera committed
501

502
503
  if (!panel->current_output)
    return;
Bastien Nocera's avatar
Bastien Nocera committed
504

505
  active = gtk_switch_get_active (panel->output_enabled_switch);
506

507
508
  if (cc_display_monitor_is_active (panel->current_output) == active)
    return;
509

510
  cc_display_monitor_set_active (panel->current_output, active);
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

  /* Prevent the invalid configuration of disabling the last monitor
   * by switching on a different one. */
  if (config_get_current_type (panel) == CC_DISPLAY_CONFIG_INVALID_NONE)
    {
      GList *outputs, *l;

      outputs = cc_display_config_get_ui_sorted_monitors (panel->current_config);
      for (l = outputs; l; l = l->next)
        {
          CcDisplayMonitor *output = CC_DISPLAY_MONITOR (l->data);

          if (output == panel->current_output)
            continue;

          if (!cc_display_monitor_is_usable (output))
            continue;

          cc_display_monitor_set_active (output, TRUE);
          cc_display_monitor_set_primary (output, TRUE);
          break;
        }
    }

  /* Changing the active state requires a UI rebuild. */
536
  rebuild_ui (panel);
Rui Matos's avatar
Rui Matos committed
537
}
Bastien Nocera's avatar
Bastien Nocera committed
538

Rui Matos's avatar
Rui Matos committed
539
static void
540
on_output_selection_combo_changed_cb (CcDisplayPanel *panel)
Rui Matos's avatar
Rui Matos committed
541
{
542
543
  GtkTreeIter iter;
  g_autoptr(CcDisplayMonitor) output = NULL;
Rui Matos's avatar
Rui Matos committed
544

545
546
  if (!panel->current_config)
    return;
Rui Matos's avatar
Rui Matos committed
547

548
549
  if (!gtk_combo_box_get_active_iter (panel->output_selection_combo, &iter))
    return;
Rui Matos's avatar
Rui Matos committed
550

551
552
553
  gtk_tree_model_get (GTK_TREE_MODEL (panel->output_selection_list), &iter,
                      1, &output,
                      -1);
Rui Matos's avatar
Rui Matos committed
554

555
  set_current_output (panel, output, FALSE);
Rui Matos's avatar
Rui Matos committed
556
557
}

558
static void
559
on_output_selection_two_toggled_cb (CcDisplayPanel *panel, GtkRadioButton *btn)
Rui Matos's avatar
Rui Matos committed
560
{
561
562
563
564
565
  CcDisplayMonitor *output;

  if (panel->rebuilding_counter > 0)
    return;

566
567
  if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (btn)))
    return;
Rui Matos's avatar
Rui Matos committed
568

569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
  output = g_object_get_data (G_OBJECT (btn), "display");

  /* Stay in single mode when we are in single mode.
   * This UI must never cause a switch between the configuration type.
   * this is in contrast to the combobox monitor selection, which may
   * switch to a disabled output both in SINGLE/MULTI mode without
   * anything changing.
   */
  if (cc_panel_get_selected_type (panel) == CC_DISPLAY_CONFIG_SINGLE)
    {
      if (panel->current_output)
        cc_display_monitor_set_active (panel->current_output, FALSE);
      if (output)
        cc_display_monitor_set_active (output, TRUE);

      update_apply_button (panel);
    }

587
  set_current_output (panel, g_object_get_data (G_OBJECT (btn), "display"), FALSE);
588
589
590
}

static void
591
on_primary_display_selected_index_changed_cb (CcDisplayPanel *panel)
592
{
593
594
  gint idx = hdy_combo_row_get_selected_index (panel->primary_display_row);
  g_autoptr(CcDisplayMonitor) output = NULL;
595

596
  if (idx < 0 || panel->rebuilding_counter > 0)
597
    return;
Rui Matos's avatar
Rui Matos committed
598

599
  output = g_list_model_get_item (G_LIST_MODEL (panel->primary_display_list), idx);
Rui Matos's avatar
Rui Matos committed
600

601
602
  if (cc_display_monitor_is_primary (output))
    return;
Rui Matos's avatar
Rui Matos committed
603
604
605
606
607

  cc_display_monitor_set_primary (output, TRUE);
  update_apply_button (panel);
}

608
609
static void
cc_display_panel_constructed (GObject *object)
Rui Matos's avatar
Rui Matos committed
610
{
611
612
  g_signal_connect_object (cc_panel_get_shell (CC_PANEL (object)), "notify::active-panel",
                           G_CALLBACK (active_panel_changed), object, 0);
Rui Matos's avatar
Rui Matos committed
613

614
  G_OBJECT_CLASS (cc_display_panel_parent_class)->constructed (object);
Rui Matos's avatar
Rui Matos committed
615
616
}

617
618
static const char *
cc_display_panel_get_help_uri (CcPanel *panel)
Rui Matos's avatar
Rui Matos committed
619
{
620
  return "help:gnome-help/prefs-display";
Rui Matos's avatar
Rui Matos committed
621
622
}

623
624
625
626
627
628
629
630
static GtkWidget *
cc_display_panel_get_title_widget (CcPanel *panel)
{
  CcDisplayPanel *self = CC_DISPLAY_PANEL (panel);

  return self->stack_switcher;
}

631
632
static void
cc_display_panel_class_init (CcDisplayPanelClass *klass)
Rui Matos's avatar
Rui Matos committed
633
{
634
635
636
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  CcPanelClass *panel_class = CC_PANEL_CLASS (klass);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
Rui Matos's avatar
Rui Matos committed
637

638
639
  g_type_ensure (CC_TYPE_NIGHT_LIGHT_PAGE);

640
  panel_class->get_help_uri = cc_display_panel_get_help_uri;
641
  panel_class->get_title_widget = cc_display_panel_get_title_widget;
Rui Matos's avatar
Rui Matos committed
642

643
644
  object_class->constructed = cc_display_panel_constructed;
  object_class->dispose = cc_display_panel_dispose;
Rui Matos's avatar
Rui Matos committed
645

646
647
648
649
650
651
652
653
654
655
  gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/display/cc-display-panel.ui");

  gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, arrangement_frame);
  gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, arrangement_bin);
  gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, config_type_switcher_frame);
  gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, config_type_join);
  gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, config_type_mirror);
  gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, config_type_single);
  gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, current_output_label);
  gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, display_settings_frame);
656
  gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, night_light_page);
657
658
659
660
661
662
  gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, output_enabled_switch);
  gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, output_selection_combo);
  gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, output_selection_stack);
  gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, output_selection_two_first);
  gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, output_selection_two_second);
  gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, primary_display_row);
663
  gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, stack_switcher);
664
665
666
667
668
669
670

  gtk_widget_class_bind_template_callback (widget_class, on_config_type_toggled_cb);
  gtk_widget_class_bind_template_callback (widget_class, on_night_light_list_box_row_activated_cb);
  gtk_widget_class_bind_template_callback (widget_class, on_output_enabled_active_changed_cb);
  gtk_widget_class_bind_template_callback (widget_class, on_output_selection_combo_changed_cb);
  gtk_widget_class_bind_template_callback (widget_class, on_output_selection_two_toggled_cb);
  gtk_widget_class_bind_template_callback (widget_class, on_primary_display_selected_index_changed_cb);
Rui Matos's avatar
Rui Matos committed
671
672
673
}

static void
674
675
676
set_current_output (CcDisplayPanel   *panel,
                    CcDisplayMonitor *output,
                    gboolean          force)
Rui Matos's avatar
Rui Matos committed
677
{
678
679
  GtkTreeIter iter;
  gboolean changed;
Rui Matos's avatar
Rui Matos committed
680

681
682
  /* Note, this function is also called if the internal UI needs updating after a rebuild. */
  changed = (output != panel->current_output);
Rui Matos's avatar
Rui Matos committed
683

684
685
  if (!changed && !force)
    return;
Rui Matos's avatar
Rui Matos committed
686

687
688
  panel->rebuilding_counter++;

689
  panel->current_output = output;
Rui Matos's avatar
Rui Matos committed
690

691
  if (panel->current_output)
Rui Matos's avatar
Rui Matos committed
692
    {
693
694
      gtk_label_set_text (panel->current_output_label, cc_display_monitor_get_ui_name (panel->current_output));
      gtk_switch_set_active (panel->output_enabled_switch, cc_display_monitor_is_active (panel->current_output));
695
      gtk_widget_set_sensitive (GTK_WIDGET (panel->output_enabled_switch), cc_display_monitor_is_usable (panel->current_output));
696
697
698
699
700
    }
  else
    {
      gtk_label_set_text (panel->current_output_label, "");
      gtk_switch_set_active (panel->output_enabled_switch, FALSE);
701
      gtk_widget_set_sensitive (GTK_WIDGET (panel->output_enabled_switch), FALSE);
Rui Matos's avatar
Rui Matos committed
702
703
    }

704
705
706
707
  if (g_object_get_data (G_OBJECT (panel->output_selection_two_first), "display") == output)
    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (panel->output_selection_two_first), TRUE);
  if (g_object_get_data (G_OBJECT (panel->output_selection_two_second), "display") == output)
    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (panel->output_selection_two_second), TRUE);
Rui Matos's avatar
Rui Matos committed
708

709
710
  gtk_tree_model_get_iter_first (GTK_TREE_MODEL (panel->output_selection_list), &iter);
  while (gtk_list_store_iter_is_valid (panel->output_selection_list, &iter))
Rui Matos's avatar
Rui Matos committed
711
    {
712
      g_autoptr(CcDisplayMonitor) o = NULL;
Rui Matos's avatar
Rui Matos committed
713

714
715
716
      gtk_tree_model_get (GTK_TREE_MODEL (panel->output_selection_list), &iter,
                          1, &o,
                          -1);
Rui Matos's avatar
Rui Matos committed
717

718
      if (o == panel->current_output)
Rui Matos's avatar
Rui Matos committed
719
        {
720
721
          gtk_combo_box_set_active_iter (panel->output_selection_combo, &iter);
          break;
Rui Matos's avatar
Rui Matos committed
722
723
        }

724
      gtk_tree_model_iter_next (GTK_TREE_MODEL (panel->output_selection_list), &iter);
Rui Matos's avatar
Rui Matos committed
725
726
    }

727
728
729
730
731
  if (changed)
    {
      cc_display_settings_set_selected_output (panel->settings, panel->current_output);
      cc_display_arrangement_set_selected_output (panel->arrangement, panel->current_output);
    }
732
733

  panel->rebuilding_counter--;
Rui Matos's avatar
Rui Matos committed
734
735
}

736
737
static void
rebuild_ui (CcDisplayPanel *panel)
Rui Matos's avatar
Rui Matos committed
738
{
739
740
  guint n_outputs, n_active_outputs, n_usable_outputs;
  GList *outputs, *l;
741
  CcDisplayConfigType type;
Rui Matos's avatar
Rui Matos committed
742

743
  panel->rebuilding_counter++;
Rui Matos's avatar
Rui Matos committed
744

745
746
  g_list_store_remove_all (panel->primary_display_list);
  gtk_list_store_clear (panel->output_selection_list);
Rui Matos's avatar
Rui Matos committed
747

748
749
750
751
752
753
  if (!panel->current_config)
    {
      panel->rebuilding_counter--;
      return;
    }

754
755
756
757
758
759
760
  n_active_outputs = 0;
  n_usable_outputs = 0;
  outputs = cc_display_config_get_ui_sorted_monitors (panel->current_config);
  for (l = outputs; l; l = l->next)
    {
      GtkTreeIter iter;
      CcDisplayMonitor *output = l->data;
Rui Matos's avatar
Rui Matos committed
761

762
763
764
765
766
767
768
      gtk_list_store_append (panel->output_selection_list, &iter);
      gtk_list_store_set (panel->output_selection_list,
                          &iter,
                          0, cc_display_monitor_get_ui_number_name (output),
                          1, output,
                          -1);

769
770
      if (!cc_display_monitor_is_usable (output))
        continue;
Rui Matos's avatar
Rui Matos committed
771

772
      n_usable_outputs += 1;
Rui Matos's avatar
Rui Matos committed
773

774
775
776
777
778
779
780
781
782
      if (n_usable_outputs == 1)
        {
          gtk_button_set_label (GTK_BUTTON (panel->output_selection_two_first),
                                cc_display_monitor_get_ui_name (output));
          g_object_set_data (G_OBJECT (panel->output_selection_two_first),
                             "display",
                             output);
        }
      else if (n_usable_outputs == 2)
783
        {
784
785
786
787
788
789
          gtk_button_set_label (GTK_BUTTON (panel->output_selection_two_second),
                                cc_display_monitor_get_ui_name (output));
          g_object_set_data (G_OBJECT (panel->output_selection_two_second),
                             "display",
                             output);
        }
Rui Matos's avatar
Rui Matos committed
790

791
792
793
794
795
796
797
798
799
800
801
802
803
804
      if (cc_display_monitor_is_active (output))
        {
          n_active_outputs += 1;

          g_list_store_append (panel->primary_display_list, output);
          if (cc_display_monitor_is_primary (output))
            hdy_combo_row_set_selected_index (panel->primary_display_row,
                                              g_list_model_get_n_items (G_LIST_MODEL (panel->primary_display_list)) - 1);

          /* Ensure that an output is selected; note that this doesn't ensure
           * the selected output is any useful (i.e. when switching types).
           */
          if (!panel->current_output)
            set_current_output (panel, output, FALSE);
805
        }
Rui Matos's avatar
Rui Matos committed
806
807
    }

808
809
  /* Sync the rebuild lists/buttons */
  set_current_output (panel, panel->current_output, TRUE);
Rui Matos's avatar
Rui Matos committed
810

811
  n_outputs = g_list_length (outputs);
812
  type = config_get_current_type (panel);
813

814
  if (n_outputs == 2 && n_usable_outputs == 2)
Rui Matos's avatar
Rui Matos committed
815
    {
816
817
818
819
820
821
822
      /* We only show the top chooser with two monitors that are
       * both usable (i.e. two monitors incl. internal and lid not closed).
       * In this case, the arrangement widget is shown if we are in JOIN mode.
       */
      if (type > CC_DISPLAY_CONFIG_LAST_VALID)
        type = CC_DISPLAY_CONFIG_JOIN;

823
      gtk_widget_set_visible (panel->config_type_switcher_frame, TRUE);
824
      gtk_widget_set_visible (panel->arrangement_frame, type == CC_DISPLAY_CONFIG_JOIN);
Rui Matos's avatar
Rui Matos committed
825

826
827
828
829
830
      /* We need a switcher except in CLONE mode */
      if (type == CC_DISPLAY_CONFIG_CLONE)
        gtk_stack_set_visible_child_name (panel->output_selection_stack, "no-selection");
      else
        gtk_stack_set_visible_child_name (panel->output_selection_stack, "two-selection");
Rui Matos's avatar
Rui Matos committed
831
    }
832
  else if (n_usable_outputs > 1)
Rui Matos's avatar
Rui Matos committed
833
    {
834
835
836
      /* We have more than one usable monitor. In this case there is no chooser,
       * and we always show the arrangement widget even if we are in SINGLE mode.
       */
837
      gtk_widget_set_visible (panel->config_type_switcher_frame, FALSE);
838
      gtk_widget_set_visible (panel->arrangement_frame, TRUE);
Rui Matos's avatar
Rui Matos committed
839

840
841
842
      /* Mirror is also invalid as it cannot be configured using this UI. */
      if (type == CC_DISPLAY_CONFIG_CLONE || type > CC_DISPLAY_CONFIG_LAST_VALID)
        type = CC_DISPLAY_CONFIG_JOIN;
843

844
      gtk_stack_set_visible_child_name (panel->output_selection_stack, "multi-selection");
845
    }
Rui Matos's avatar
Rui Matos committed
846
847
  else
    {
848
849
850
851
852
853
854
855
856
      /* We only have a single usable monitor, show neither configuration type
       * switcher nor arrangement widget and ensure we really are in SINGLE
       * mode (and not e.g. mirroring across one display) */
      type = CC_DISPLAY_CONFIG_SINGLE;

      gtk_widget_set_visible (panel->config_type_switcher_frame, FALSE);
      gtk_widget_set_visible (panel->arrangement_frame, FALSE);

      gtk_stack_set_visible_child_name (panel->output_selection_stack, "no-selection");
Rui Matos's avatar
Rui Matos committed
857
858
    }

859
860
  cc_panel_set_selected_type (panel, type);

861
  panel->rebuilding_counter--;
862
  update_apply_button (panel);
Rui Matos's avatar
Rui Matos committed
863
864
865
}

static void
866
reset_current_config (CcDisplayPanel *panel)
Rui Matos's avatar
Rui Matos committed
867
{
868
  CcDisplayConfig *current;
869
870
  CcDisplayConfig *old;
  GList *outputs, *l;
Rui Matos's avatar
Rui Matos committed
871

872
873
874
875
  g_debug ("Resetting current config!");

  /* We need to hold on to the config until all display references are dropped. */
  old = panel->current_config;
876
  panel->current_output = NULL;
Rui Matos's avatar
Rui Matos committed
877

878
  current = cc_display_config_manager_get_current (panel->manager);
879
  cc_display_config_set_minimum_size (current, MINIMUM_WIDTH, MINIMUM_HEIGHT);
880
  panel->current_config = current;
Rui Matos's avatar
Rui Matos committed
881

882
883
884
885
  g_list_store_remove_all (panel->primary_display_list);
  gtk_list_store_clear (panel->output_selection_list);

  if (panel->current_config)
Rui Matos's avatar
Rui Matos committed
886
    {
887
888
889
890
      outputs = cc_display_config_get_ui_sorted_monitors (panel->current_config);
      for (l = outputs; l; l = l->next)
        {
          CcDisplayMonitor *output = l->data;
891

892
893
894
895
896
          /* Mark any builtin monitor as unusable if the lid is closed. */
          if (cc_display_monitor_is_builtin (output) && panel->lid_is_closed)
            cc_display_monitor_set_usable (output, FALSE);
        }
    }
897

898
899
900
  cc_display_arrangement_set_config (panel->arrangement, panel->current_config);
  cc_display_settings_set_config (panel->settings, panel->current_config);
  set_current_output (panel, NULL, FALSE);
901

902
903
904
  g_clear_object (&old);

  update_apply_button (panel);
905
906
907
908
909
}

static void
on_screen_changed (CcDisplayPanel *panel)
{
910
  if (!panel->manager)
911
912
913
914
915
    return;

  reset_titlebar (panel);

  reset_current_config (panel);
916
  rebuild_ui (panel);
917

918
  if (!panel->current_config)
919
    return;
920
921

  ensure_monitor_labels (panel);
Rui Matos's avatar
Rui Matos committed
922
923
}

924
925
926
927
928
929
930
931
932
933
934
static gboolean
on_toplevel_key_press (GtkWidget   *button,
                       GdkEventKey *event)
{
  if (event->keyval != GDK_KEY_Escape)
    return GDK_EVENT_PROPAGATE;

  g_signal_emit_by_name (button, "activate");
  return GDK_EVENT_STOP;
}

Rui Matos's avatar
Rui Matos committed
935
static void
936
show_apply_titlebar (CcDisplayPanel *panel, gboolean is_applicable)
Rui Matos's avatar
Rui Matos committed
937
{
938
  if (!panel->apply_titlebar)
939
    {
940
      g_autoptr(GtkSizeGroup) size_group = NULL;
941
      GtkWidget *header, *button, *toplevel;
942
      panel->apply_titlebar = header = gtk_header_bar_new ();
943
      gtk_widget_show (header);
Rui Matos's avatar
Rui Matos committed
944

945
      size_group = gtk_size_group_new (GTK_SIZE_GROUP_VERTICAL);
Rui Matos's avatar
Rui Matos committed
946

947
      button = gtk_button_new_with_mnemonic (_("_Cancel"));
948
      gtk_widget_show (button);
949
950
951
952
      gtk_header_bar_pack_start (GTK_HEADER_BAR (header), button);
      gtk_size_group_add_widget (size_group, button);
      g_signal_connect_object (button, "clicked", G_CALLBACK (on_screen_changed),
                               panel, G_CONNECT_SWAPPED);
Rui Matos's avatar
Rui Matos committed
953

954
955
956
      toplevel = cc_shell_get_toplevel (cc_panel_get_shell (CC_PANEL (panel)));
      g_signal_connect_object (toplevel, "key-press-event", G_CALLBACK (on_toplevel_key_press),
                               button, G_CONNECT_SWAPPED);
Rui Matos's avatar
Rui Matos committed
957

958
      panel->apply_titlebar_apply = button = gtk_button_new_with_mnemonic (_("_Apply"));
959
      gtk_widget_show (button);
960
961
962
963
964
965
      gtk_header_bar_pack_end (GTK_HEADER_BAR (header), button);
      gtk_size_group_add_widget (size_group, button);
      g_signal_connect_object (button, "clicked", G_CALLBACK (apply_current_configuration),
                               panel, G_CONNECT_SWAPPED);
      gtk_style_context_add_class (gtk_widget_get_style_context (button),
                                   GTK_STYLE_CLASS_SUGGESTED_ACTION);
966

967
968
      header = gtk_window_get_titlebar (GTK_WINDOW (toplevel));
      if (header)
969
        panel->main_titlebar = g_object_ref (header);
970
      panel->main_title = g_strdup (gtk_window_get_title (GTK_WINDOW (toplevel)));
Rui Matos's avatar
Rui Matos committed
971

972
973
974
      gtk_window_set_titlebar (GTK_WINDOW (toplevel), panel->apply_titlebar);
      g_object_ref (panel->apply_titlebar);
      g_object_ref (panel->apply_titlebar_apply);
975
    }
Rui Matos's avatar
Rui Matos committed
976

977
978
  if (is_applicable)
    {
979
980
      gtk_header_bar_set_title (GTK_HEADER_BAR (panel->apply_titlebar), _("Apply Changes?"));
      gtk_header_bar_set_subtitle (GTK_HEADER_BAR (panel->apply_titlebar), NULL);
981
982
983
    }
  else
    {
984
      gtk_header_bar_set_title (GTK_HEADER_BAR (panel->apply_titlebar), _("Changes Cannot be Applied"));
985
      gtk_header_bar_set_subtitle (GTK_HEADER_BAR (panel->apply_titlebar), _("This could be due to hardware/software limitations."));
986
    }
987
  gtk_widget_set_sensitive (panel->apply_titlebar_apply, is_applicable);
Rui Matos's avatar
Rui Matos committed
988
989
}

990
991
992
993
static void
update_apply_button (CcDisplayPanel *panel)
{
  gboolean config_equal;
994
  g_autoptr(CcDisplayConfig) applied_config = NULL;
995

996
997
998
999
1000
1001
  if (!panel->current_config)
    {
      reset_titlebar (panel);
      return;
    }

1002
  applied_config = cc_display_config_manager_get_current (panel->manager);
1003

1004
  config_equal = cc_display_config_equal (panel->current_config,
1005
                                          applied_config);
1006

Rui Matos's avatar
Rui Matos committed
1007
1008
1009
  if (config_equal)
    reset_titlebar (panel);
  else
1010
    show_apply_titlebar (panel, cc_display_config_is_applicable (panel->current_config));
1011
1012
}

1013
1014
1015
static void
apply_current_configuration (CcDisplayPanel *self)
{
1016
  g_autoptr(GError) error = NULL;
1017

1018
  cc_display_config_apply (self->current_config, &error);
Bastien Nocera's avatar
Bastien Nocera committed
1019

1020
1021
  /* re-read the configuration */
  on_screen_changed (self);
Bastien Nocera's avatar
Bastien Nocera committed
1022

1023
  if (error)