/* cc-keyboard-shortcut-dialog.c
 *
 * Copyright (C) 2010 Intel, Inc
 * Copyright (C) 2016 Endless, Inc
 * Copyright (C) 2020 System76, 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: Thomas Wood <thomas.wood@intel.com>
 *         Georges Basile Stavracas Neto <gbsneto@gnome.org>
 *         Ian Douglas Scott <idscott@system76.com>
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include <config.h>
#include <glib/gi18n.h>
#include <adwaita.h>

#include "cc-keyboard-shortcut-dialog.h"
#include "cc-keyboard-item.h"
#include "cc-keyboard-manager.h"
#include "cc-keyboard-shortcut-editor.h"
#include "cc-keyboard-shortcut-row.h"
#include "cc-list-row.h"
#include "cc-util.h"
#include "keyboard-shortcuts.h"

#define SHORTCUT_DELIMITERS "+ "

typedef struct {
  gchar          *section_title;
  gchar          *section_id;
  guint           modified_count;
  GtkLabel       *modified_label;
} SectionRowData;

typedef struct {
  CcKeyboardItem *item;
  gchar          *section_title;
  gchar          *section_id;
  SectionRowData *section_data;
} ShortcutRowData;

struct _CcKeyboardShortcutDialog
{
  GtkDialog  parent_instance;

  GtkSizeGroup       *accelerator_sizegroup;
  GtkWidget          *back_button;
  GtkListBoxRow      *custom_shortcut_add_row;
  guint               custom_shortcut_count;
  GtkWidget          *empty_custom_shortcuts_placeholder;
  GtkWidget          *empty_search_placeholder;
  GtkHeaderBar       *headerbar;
  GtkStack           *header_stack;
  GtkWidget          *reset_all_button;
  GtkWidget          *section_box;
  GtkSearchEntry     *search_entry;
  GtkListBox         *section_listbox;
  GtkListBoxRow      *section_row;
  GtkWidget          *shortcut_box;
  GtkListBox         *shortcut_listbox;
  GtkStack           *stack;

  CcKeyboardManager  *manager;
  GtkWidget          *shortcut_editor;
  GHashTable         *sections;
 };

G_DEFINE_TYPE (CcKeyboardShortcutDialog, cc_keyboard_shortcut_dialog, GTK_TYPE_DIALOG)
static gboolean
is_matched_shortcut_present (GtkListBox *listbox,
                             gpointer user_data);

static SectionRowData*
section_row_data_new (const gchar *section_id,
                      const gchar *section_title,
                      GtkLabel    *modified_label)
{
  SectionRowData *data;

  data = g_new0 (SectionRowData, 1);
  data->section_id = g_strdup (section_id);
  data->section_title = g_strdup (section_title);
  data->modified_count = 0;
  data->modified_label = modified_label;

  return data;
}

static void
section_row_data_free (SectionRowData *data)
{
  g_free (data->section_id);
  g_free (data->section_title);
  g_free (data);
}

static ShortcutRowData*
shortcut_row_data_new (CcKeyboardItem *item,
                       const gchar    *section_id,
                       const gchar    *section_title,
                       SectionRowData *section_data)
{
  ShortcutRowData *data;

  data = g_new0 (ShortcutRowData, 1);
  data->item = g_object_ref (item);
  data->section_id = g_strdup (section_id);
  data->section_title = g_strdup (section_title);
  data->section_data = section_data;

  return data;
}

static void
shortcut_row_data_free (ShortcutRowData *data)
{
  g_object_unref (data->item);
  g_free (data->section_id);
  g_free (data->section_title);
  g_free (data);
}

static GtkListBoxRow*
add_section (CcKeyboardShortcutDialog *self,
             const gchar     *section_id,
             const gchar     *section_title)
{
  GtkWidget *icon, *modified_label, *row;

  icon = gtk_image_new_from_icon_name ("go-next-symbolic");
  gtk_widget_add_css_class (icon, "dim-label");

  modified_label = gtk_label_new (NULL);
  gtk_widget_add_css_class (modified_label, "dim-label");

  row = adw_action_row_new ();
  gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), TRUE);
  adw_preferences_row_set_title (ADW_PREFERENCES_ROW (row), _(section_title));
  //TODO gtk_container_add (GTK_CONTAINER (row), modified_label);
  //TODO gtk_container_add (GTK_CONTAINER (row), icon);

  g_object_set_data_full (G_OBJECT (row),
                          "data",
                          section_row_data_new (section_id, section_title, GTK_LABEL (modified_label)),
                          (GDestroyNotify)section_row_data_free);

  g_hash_table_insert (self->sections, g_strdup (section_id), row);
  gtk_list_box_append (self->section_listbox, row);

  return GTK_LIST_BOX_ROW (row);
}

static void
set_custom_shortcut_placeholder_visibility (CcKeyboardShortcutDialog *self)
{
  SectionRowData *section_data;
  gboolean is_custom_shortcuts = FALSE;

  if (self->section_row != NULL)
    {
      section_data = g_object_get_data (G_OBJECT (self->section_row), "data");
      is_custom_shortcuts = (strcmp (section_data->section_id, "custom") == 0);

      gtk_stack_set_transition_type (self->stack, GTK_STACK_TRANSITION_TYPE_CROSSFADE);
      if (is_custom_shortcuts && (self->custom_shortcut_count == 0))
        gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->empty_custom_shortcuts_placeholder));
      else
        gtk_stack_set_visible_child (self->stack, self->shortcut_box);
    }
}
 
static void
add_item (CcKeyboardShortcutDialog *self,
          CcKeyboardItem  *item,
          const gchar     *section_id,
          const gchar     *section_title)
{
  GtkWidget *row;
  GtkListBoxRow *section_row;
  SectionRowData *section_data;

  section_row = g_hash_table_lookup (self->sections, section_id);
  if (section_row == NULL)
    section_row = add_section (self, section_id, section_title);

  section_data = g_object_get_data (G_OBJECT (section_row), "data");

  row = GTK_WIDGET (cc_keyboard_shortcut_row_new (item,
                                                  self->manager,
                                                  CC_KEYBOARD_SHORTCUT_EDITOR (self->shortcut_editor),
		                                  self->accelerator_sizegroup));

  g_object_set_data_full (G_OBJECT (row),
                          "data",
                          shortcut_row_data_new (item, section_id, section_title, section_data),
                          (GDestroyNotify)shortcut_row_data_free);

  if (strcmp (section_id, "custom") == 0)
    {
      self->custom_shortcut_count++;
      set_custom_shortcut_placeholder_visibility (self);
    }

  gtk_list_box_append (self->shortcut_listbox, row);
}

static void
remove_item (CcKeyboardShortcutDialog *self,
             CcKeyboardItem  *item)
{
  GtkWidget *child;

  for (child = gtk_widget_get_first_child (GTK_WIDGET (self->shortcut_listbox));
       child;
       child = gtk_widget_get_next_sibling (child))
    {
      ShortcutRowData *row_data;

      if (!GTK_IS_LIST_BOX_ROW (child))
        continue;

      row_data = g_object_get_data (G_OBJECT (child), "data");

      if (row_data && row_data->item == item)
        {
          if (strcmp (row_data->section_id, "custom") == 0)
            {
              self->custom_shortcut_count--;
              set_custom_shortcut_placeholder_visibility (self);
            }

          gtk_list_box_remove (self->shortcut_listbox, child);
          break;
        }
    }
}

static void
update_modified_counts (CcKeyboardShortcutDialog *self)
{
  SectionRowData *section_data;
  ShortcutRowData *shortcut_data;
  g_autofree gchar *modified_text = NULL;
  GtkWidget *child;

  for (child = gtk_widget_get_first_child (GTK_WIDGET (self->section_listbox));
       child;
       child = gtk_widget_get_next_sibling (child))
    {
      if (!GTK_IS_LIST_BOX_ROW (child))
        continue;

      section_data = g_object_get_data (G_OBJECT (child), "data");
      section_data->modified_count = 0;
    }

  for (child = gtk_widget_get_first_child (GTK_WIDGET (self->shortcut_listbox));
       child;
       child = gtk_widget_get_next_sibling (child))
    {
      if (!GTK_IS_LIST_BOX_ROW (child))
        continue;

      if (GTK_LIST_BOX_ROW (child) == self->custom_shortcut_add_row)
        continue;
      shortcut_data = g_object_get_data (G_OBJECT (child), "data");
      if (!cc_keyboard_item_is_value_default (shortcut_data->item))
        shortcut_data->section_data->modified_count++;
    }

  for (child = gtk_widget_get_first_child (GTK_WIDGET (self->section_listbox));
       child;
       child = gtk_widget_get_next_sibling (child))
    {
      if (!GTK_IS_LIST_BOX_ROW (child))
        continue;

      section_data = g_object_get_data (G_OBJECT (child), "data");
      if (section_data->modified_count > 0)
        {
          modified_text = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE,
                                                        "%d modified",
                                                        "%d modified",
                                                        section_data->modified_count),
                                           section_data->modified_count);
          gtk_label_set_text (section_data->modified_label, modified_text);
        }
      else
        {
          gtk_label_set_text (section_data->modified_label, "");
        }
    }
}

static void
show_section_list (CcKeyboardShortcutDialog *self)
{
  if (self->section_row != NULL)
    gtk_stack_set_transition_type (self->stack, GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
  else
    gtk_stack_set_transition_type (self->stack, GTK_STACK_TRANSITION_TYPE_NONE);
  self->section_row = NULL;

  gtk_stack_set_visible_child (self->stack, self->section_box);
  gtk_window_set_title (GTK_WINDOW (self), _("Keyboard Shortcuts"));
  gtk_editable_set_text (GTK_EDITABLE (self->search_entry), "");
  gtk_stack_set_visible_child (self->header_stack, self->reset_all_button);
  gtk_widget_set_visible (GTK_WIDGET (self->search_entry), TRUE);

  update_modified_counts (self);
}

static void
show_shortcut_list (CcKeyboardShortcutDialog *self)
{
  SectionRowData *section_data;
  gchar *title;
  gboolean is_custom_shortcuts = FALSE;

   title = _("Keyboard Shortcuts");
   gtk_stack_set_transition_type (self->stack, GTK_STACK_TRANSITION_TYPE_NONE);
   if (self->section_row != NULL)
    {
      section_data = g_object_get_data (G_OBJECT (self->section_row), "data");
      title = _(section_data->section_title);
      is_custom_shortcuts = (strcmp (section_data->section_id, "custom") == 0);
      gtk_stack_set_transition_type (self->stack, GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT);
    }
  gtk_list_box_invalidate_filter (self->shortcut_listbox);

  if (is_custom_shortcuts && (self->custom_shortcut_count == 0))
    gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->empty_custom_shortcuts_placeholder));
  else
    gtk_stack_set_visible_child (self->stack, self->shortcut_box);

  gtk_window_set_title (GTK_WINDOW (self), title);
  set_custom_shortcut_placeholder_visibility (self);
  gtk_stack_set_visible_child (self->header_stack, self->back_button);
  gtk_widget_set_visible (GTK_WIDGET (self->search_entry), self->section_row == NULL);

}

static void
add_custom_shortcut_clicked_cb (CcKeyboardShortcutDialog *self)
{
  CcKeyboardShortcutEditor *editor;

  editor = CC_KEYBOARD_SHORTCUT_EDITOR (self->shortcut_editor);

  cc_keyboard_shortcut_editor_set_mode (editor, CC_SHORTCUT_EDITOR_CREATE);
  cc_keyboard_shortcut_editor_set_item (editor, NULL);

  gtk_widget_show (self->shortcut_editor);
}

static void
section_row_activated (GtkWidget                *button,
                       GtkListBoxRow            *row,
                       CcKeyboardShortcutDialog *self)
{
  self->section_row = row;
  show_shortcut_list (self);
}

static void
shortcut_row_activated (GtkWidget                *button,
                        GtkListBoxRow            *row,
                        CcKeyboardShortcutDialog *self)
{
  CcKeyboardShortcutEditor *editor;

  if (row == self->custom_shortcut_add_row)
    {
      add_custom_shortcut_clicked_cb (self);
      return;
    }

  editor = CC_KEYBOARD_SHORTCUT_EDITOR (self->shortcut_editor);

  ShortcutRowData *data = g_object_get_data (G_OBJECT (row), "data");

  cc_keyboard_shortcut_editor_set_mode (editor, CC_SHORTCUT_EDITOR_EDIT);
  cc_keyboard_shortcut_editor_set_item (editor, data->item);

  gtk_widget_show (self->shortcut_editor);
}

static void
back_button_clicked_cb (CcKeyboardShortcutDialog *self)
{
  show_section_list (self);
}

static void
reset_shortcut (CcKeyboardShortcutDialog *self,
                GtkWidget                *row)
{
  ShortcutRowData *data;

  if (row == GTK_WIDGET (self->custom_shortcut_add_row))
    return;

  data = g_object_get_data (G_OBJECT (row), "data");

  /* Don't reset custom shortcuts */
  if (cc_keyboard_item_get_item_type (data->item) == CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH)
    return;

  /* cc_keyboard_manager_reset_shortcut() already resets conflicting shortcuts,
   * so no other check is needed here. */
  cc_keyboard_manager_reset_shortcut (self->manager, data->item);
}

static void
on_reset_all_dialog_response_cb (GtkDialog                *dialog,
                                 gint                      response,
                                 CcKeyboardShortcutDialog *self)
{

  if (response == GTK_RESPONSE_ACCEPT)
    {
      GtkWidget *child;

      for (child = gtk_widget_get_first_child (GTK_WIDGET (self->shortcut_listbox));
           child;
           child = gtk_widget_get_next_sibling (child))
        {
          if (!GTK_IS_LIST_BOX_ROW (child))
            continue;
          if (GTK_LIST_BOX_ROW (child) == self->custom_shortcut_add_row)
            continue;
          reset_shortcut (self, child);
        }
    }

  gtk_window_destroy (GTK_WINDOW (dialog));
  update_modified_counts (self);
}

static void
reset_all_clicked_cb (CcKeyboardShortcutDialog *self)
{
  GtkWidget *dialog, *button;

  dialog = gtk_message_dialog_new (GTK_WINDOW (self),
                                   GTK_DIALOG_MODAL | GTK_DIALOG_USE_HEADER_BAR | GTK_DIALOG_DESTROY_WITH_PARENT,
                                   GTK_MESSAGE_WARNING,
                                   GTK_BUTTONS_NONE,
                                   _("Reset All Shortcuts?"));

  gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
                                            _("Resetting the shortcuts may affect your custom shortcuts. "
                                              "This cannot be undone."));

  gtk_dialog_add_buttons (GTK_DIALOG (dialog),
                          _("Cancel"), GTK_RESPONSE_CANCEL,
                          _("Reset All"), GTK_RESPONSE_ACCEPT,
                          NULL);

  gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_CANCEL);

  /* Make the "Reset All" button destructive */
  button = gtk_dialog_get_widget_for_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
  gtk_widget_add_css_class (button, "destructive-action");

  g_signal_connect (dialog, "response", G_CALLBACK (on_reset_all_dialog_response_cb), self);

  gtk_window_present (GTK_WINDOW (dialog));
}

static void
search_entry_cb (CcKeyboardShortcutDialog *self)
{
  gboolean is_shortcut = is_matched_shortcut_present (self->shortcut_listbox, self);
  const gchar *search_text = gtk_editable_get_text (GTK_EDITABLE (self->search_entry));

  if (!is_shortcut)
      gtk_stack_set_visible_child (self->stack, self->empty_search_placeholder);
  else if (g_utf8_strlen (search_text, -1) == 0 && self->section_row == NULL)
    show_section_list (self);
  else if (gtk_stack_get_visible_child (self->stack) != self->shortcut_box)
    show_shortcut_list (self);
  else
    gtk_list_box_invalidate_filter (self->shortcut_listbox);
}

static gboolean
strv_contains_prefix_or_match (gchar       **strv,
                               const gchar  *prefix)
{
  const struct {
    const gchar *key;
    const gchar *untranslated;
    const gchar *synonym;
  } key_aliases[] =
    {
      { "ctrl",   "Ctrl",  "ctrl" },
      { "win",    "Super", "super" },
      { "option",  NULL,   "alt" },
      { "command", NULL,   "super" },
      { "apple",   NULL,   "super" },
    };

  for (guint i = 0; strv[i]; i++)
    {
      if (g_str_has_prefix (strv[i], prefix))
        return TRUE;
    }

  for (guint i = 0; i < G_N_ELEMENTS (key_aliases); i++)
    {
      g_autofree gchar *alias = NULL;
      const gchar *synonym;

      if (!g_str_has_prefix (key_aliases[i].key, prefix))
        continue;

      if (key_aliases[i].untranslated)
        {
          const gchar *translated_label;

          /* Steal GTK+'s translation */
          translated_label = g_dpgettext2 ("gtk40", "keyboard label", key_aliases[i].untranslated);
          alias = g_utf8_strdown (translated_label, -1);
        }

      synonym = key_aliases[i].synonym;

      /* If a translation or synonym of the key is in the accelerator, and we typed
       * the key, also consider that a prefix */
      if ((alias && g_strv_contains ((const gchar * const *) strv, alias)) ||
          (synonym && g_strv_contains ((const gchar * const *) strv, synonym)))
        {
          return TRUE;
        }
    }

  return FALSE;
}

static gboolean
search_match_shortcut (CcKeyboardItem *item,
                       const gchar    *search)
{
  g_auto(GStrv) shortcut_tokens = NULL, search_tokens = NULL;
  g_autofree gchar *normalized_accel = NULL;
  g_autofree gchar *accel = NULL;
  gboolean match;
  GList *key_combos;
  CcKeyCombo *combo;

  key_combos = cc_keyboard_item_get_key_combos (item);
  for (GList *l = key_combos; l != NULL; l = l->next)
    {
      combo = l->data;

      if (is_empty_binding (combo))
        continue;

      match = TRUE;
      accel = convert_keysym_state_to_string (combo);
      normalized_accel = cc_util_normalize_casefold_and_unaccent (accel);

      shortcut_tokens = g_strsplit_set (normalized_accel, SHORTCUT_DELIMITERS, -1);
      search_tokens = g_strsplit_set (search, SHORTCUT_DELIMITERS, -1);

      for (guint i = 0; search_tokens[i] != NULL; i++)
        {
          const gchar *token;

          /* Strip leading and trailing whitespaces */
          token = g_strstrip (search_tokens[i]);

          if (g_utf8_strlen (token, -1) == 0)
            continue;

          match = match && strv_contains_prefix_or_match (shortcut_tokens, token);

          if (!match)
            break;
        }

      if (match)
        return TRUE;
    }

  return FALSE;
}

static gint
section_sort_function (GtkListBoxRow *a,
                       GtkListBoxRow *b,
                       gpointer       user_data)
{
  SectionRowData *a_data, *b_data;

  a_data = g_object_get_data (G_OBJECT (a), "data");
  b_data = g_object_get_data (G_OBJECT (b), "data");

  /* Put custom shortcuts below everything else */
  if (g_strcmp0 (a_data->section_id, "custom") == 0)
    return 1;

  return g_strcmp0 (a_data->section_title, b_data->section_title);
}

static gint
shortcut_sort_function (GtkListBoxRow *a,
                        GtkListBoxRow *b,
                        gpointer       user_data)
{
  CcKeyboardShortcutDialog *self = user_data;
  ShortcutRowData *a_data, *b_data;
  gint retval;

  if (a == self->custom_shortcut_add_row)
    return 1;
  else if (b == self->custom_shortcut_add_row)
    return -1;

  a_data = g_object_get_data (G_OBJECT (a), "data");
  b_data = g_object_get_data (G_OBJECT (b), "data");

  retval = g_strcmp0 (a_data->section_title, b_data->section_title);

  if (retval != 0)
    return retval;

  return g_strcmp0 (cc_keyboard_item_get_description (a_data->item), cc_keyboard_item_get_description (b_data->item));
}

static gboolean
shortcut_filter_function (GtkListBoxRow *row,
                          gpointer       userdata)
{
  CcKeyboardShortcutDialog *self = userdata;
  SectionRowData  *section_data;
  ShortcutRowData *data;
  CcKeyboardItem *item;
  gboolean retval;
  g_autofree gchar *search = NULL;
  g_autofree gchar *name = NULL;
  g_auto(GStrv) terms = NULL;
  gboolean is_custom_shortcuts = FALSE;
  const gchar *search_text;

  if (self->section_row != NULL)
  {
    section_data = g_object_get_data (G_OBJECT (self->section_row), "data");
    is_custom_shortcuts = (strcmp (section_data->section_id, "custom") == 0);

    data = g_object_get_data (G_OBJECT (row), "data");
    if (data && strcmp (data->section_id, section_data->section_id) != 0)
      return FALSE;
  }

  if (row == self->custom_shortcut_add_row)
    return is_custom_shortcuts;

  search_text = gtk_editable_get_text (GTK_EDITABLE (self->search_entry));
  if (g_utf8_strlen (search_text, -1) == 0)
    return TRUE;

  data = g_object_get_data (G_OBJECT (row), "data");
  item = data->item;
  name = cc_util_normalize_casefold_and_unaccent (cc_keyboard_item_get_description (item));
  search = cc_util_normalize_casefold_and_unaccent (search_text);
  terms = g_strsplit (search, " ", -1);

  for (guint i = 0; terms && terms[i]; i++)
    {
      retval = strstr (name, terms[i]) || search_match_shortcut (item, terms[i]);
      if (!retval)
        break;
    }

  return retval;
}

static gboolean
is_matched_shortcut_present (GtkListBox* listbox,
                             gpointer user_data)
{
  for (gint i = 0; ; i++)
    {
      GtkListBoxRow *current = gtk_list_box_get_row_at_index (listbox, i);
      if (!current)
        return FALSE;
      if (shortcut_filter_function (current, user_data))
        return TRUE;
    }
}

static void
shortcut_header_function (GtkListBoxRow *row,
                          GtkListBoxRow *before,
                          gpointer       user_data)
{
  CcKeyboardShortcutDialog *self = user_data;
  gboolean add_header;
  ShortcutRowData *data, *before_data;

  data = g_object_get_data (G_OBJECT (row), "data");

  if (row == self->custom_shortcut_add_row)
    {

      add_header = FALSE;
    }
  else if (before && before != self->custom_shortcut_add_row)
    {
      before_data = g_object_get_data (G_OBJECT (before), "data");
      add_header = g_strcmp0 (before_data->section_id, data->section_id) != 0;
    }
  else
    {
      add_header = TRUE;
    }

  if (self->section_row != NULL)
    add_header = FALSE;

  if (add_header)
    {
      GtkWidget *box, *label, *separator;
      g_autofree gchar *markup = NULL;

      box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
      if (!before)
        gtk_widget_set_margin_top (box, 6);

      markup = g_strdup_printf ("<b>%s</b>", _(data->section_title));
      label = g_object_new (GTK_TYPE_LABEL,
                            "label", markup,
                            "use-markup", TRUE,
                            "xalign", 0.0,
                            "margin-start", 6,
                            NULL);
      gtk_box_append (GTK_BOX (box), label);

      separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
      gtk_box_append (GTK_BOX (box), separator);

      gtk_list_box_row_set_header (row, box);
    }
  else
    {
      gtk_list_box_row_set_header (row, NULL);
    }
}

static void
cc_keyboard_shortcut_dialog_constructed (GObject *object)
{
  CcKeyboardShortcutDialog *self = CC_KEYBOARD_SHORTCUT_DIALOG (object);

  G_OBJECT_CLASS (cc_keyboard_shortcut_dialog_parent_class)->constructed (object);

  /* Setup the dialog's transient parent */
  gtk_window_set_transient_for (GTK_WINDOW (self->shortcut_editor), GTK_WINDOW (self));
}

static void
cc_keyboard_shortcut_dialog_finalize (GObject *object)
{
  CcKeyboardShortcutDialog *self = CC_KEYBOARD_SHORTCUT_DIALOG (object);

  g_clear_object (&self->manager);
  g_clear_pointer (&self->sections, g_hash_table_destroy);
  g_clear_pointer ((GtkWindow**)&self->shortcut_editor, gtk_window_destroy);
}

static void
cc_keyboard_shortcut_dialog_class_init (CcKeyboardShortcutDialogClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);

  object_class->constructed = cc_keyboard_shortcut_dialog_constructed;
  object_class->finalize = cc_keyboard_shortcut_dialog_finalize;

  gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/keyboard/cc-keyboard-shortcut-dialog.ui");

  gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutDialog, accelerator_sizegroup);
  gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutDialog, back_button);
  gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutDialog, custom_shortcut_add_row);
  gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutDialog, empty_custom_shortcuts_placeholder);
  gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutDialog, empty_search_placeholder);
  gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutDialog, headerbar);
  gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutDialog, header_stack);
  gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutDialog, reset_all_button);
  gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutDialog, search_entry);
  gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutDialog, section_listbox);
  gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutDialog, section_box);
  gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutDialog, shortcut_listbox);
  gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutDialog, shortcut_box);
  gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutDialog, stack);

  gtk_widget_class_bind_template_callback (widget_class, add_custom_shortcut_clicked_cb);
  gtk_widget_class_bind_template_callback (widget_class, back_button_clicked_cb);
  gtk_widget_class_bind_template_callback (widget_class, reset_all_clicked_cb);
  gtk_widget_class_bind_template_callback (widget_class, search_entry_cb);
  gtk_widget_class_bind_template_callback (widget_class, section_row_activated);
  gtk_widget_class_bind_template_callback (widget_class, shortcut_row_activated);
}

static void
cc_keyboard_shortcut_dialog_init (CcKeyboardShortcutDialog *self)
{
  gtk_widget_init_template (GTK_WIDGET (self));

  gtk_search_entry_set_key_capture_widget (self->search_entry, GTK_WIDGET (self));

  self->manager = cc_keyboard_manager_new ();

  self->shortcut_editor = cc_keyboard_shortcut_editor_new (self->manager);

  self->sections = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
  self->section_row = NULL;

  g_signal_connect_object (self->manager,
                           "shortcut-added",
                           G_CALLBACK (add_item),
                           self,
                           G_CONNECT_SWAPPED);

  g_signal_connect_object (self->manager,
                           "shortcut-removed",
                           G_CALLBACK (remove_item),
                           self,
                           G_CONNECT_SWAPPED);

  add_section(self, "custom", "Custom Shortcuts");
  cc_keyboard_manager_load_shortcuts (self->manager);

  gtk_list_box_set_sort_func (GTK_LIST_BOX (self->section_listbox),
                              section_sort_function,
                              self,
                              NULL);

  gtk_list_box_set_filter_func (self->shortcut_listbox,
                                shortcut_filter_function,
                                self,
                                NULL);
  gtk_list_box_set_header_func (self->shortcut_listbox,
                                shortcut_header_function,
                                self,
                                NULL);
  gtk_list_box_set_sort_func (GTK_LIST_BOX (self->shortcut_listbox),
                              shortcut_sort_function,
                              self,
                              NULL);

  show_section_list (self);
}

GtkWidget*
cc_keyboard_shortcut_dialog_new (void)
{
  return g_object_new (CC_TYPE_KEYBOARD_SHORTCUT_DIALOG,
                       "use-header-bar", 1,
                       NULL);
}
