Blob Blame History Raw
/* dzl-shortcut-theme-editor.c
 *
 * Copyright (C) 2017 Christian Hergert <chergert@redhat.com>
 *
 * 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/>.
 */

#define G_LOG_DOMAIN "dzl-shortcut-theme-editor"

#include "config.h"

#include <glib/gi18n.h>

#include "shortcuts/dzl-shortcut-accel-dialog.h"
#include "shortcuts/dzl-shortcut-model.h"
#include "shortcuts/dzl-shortcut-private.h"
#include "shortcuts/dzl-shortcut-theme-editor.h"
#include "util/dzl-util-private.h"

typedef struct
{
  GtkTreeView         *tree_view;
  GtkSearchEntry      *filter_entry;
  GtkTreeViewColumn   *shortcut_column;
  GtkCellRendererText *shortcut_cell;
  GtkTreeViewColumn   *title_column;
  GtkCellRendererText *title_cell;

  DzlShortcutTheme    *theme;
  GtkTreeModel        *model;
  GtkTreePath         *selected;
  PangoAttrList       *attrs;
} DzlShortcutThemeEditorPrivate;

enum {
  PROP_0,
  PROP_THEME,
  N_PROPS
};

enum {
  CHANGED,
  N_SIGNALS
};

G_DEFINE_TYPE_WITH_PRIVATE (DzlShortcutThemeEditor, dzl_shortcut_theme_editor, GTK_TYPE_BIN)

static GParamSpec *properties [N_PROPS];
static guint signals [N_SIGNALS];

static void
dzl_shortcut_theme_editor_dialog_response (DzlShortcutThemeEditor *self,
                                           gint                    response_code,
                                           DzlShortcutAccelDialog *dialog)
{
  DzlShortcutThemeEditorPrivate *priv = dzl_shortcut_theme_editor_get_instance_private (self);
  gboolean changed = FALSE;

  g_assert (DZL_IS_SHORTCUT_THEME_EDITOR (self));
  g_assert (DZL_SHORTCUT_ACCEL_DIALOG (dialog));

  if (response_code == GTK_RESPONSE_ACCEPT)
    {
      const DzlShortcutChord *chord = dzl_shortcut_accel_dialog_get_chord (dialog);

      if (priv->selected != NULL)
        {
          GtkTreePath *path;
          GtkTreeModel *model;
          GtkTreeIter iter;

          model = gtk_tree_view_get_model (priv->tree_view);

          if (GTK_IS_TREE_STORE (model))
            path = gtk_tree_path_copy (priv->selected);
          else
            path = gtk_tree_model_filter_convert_path_to_child_path (GTK_TREE_MODEL_FILTER (model), priv->selected);

          if (gtk_tree_model_get_iter (model, &iter, path))
            dzl_shortcut_model_set_chord (DZL_SHORTCUT_MODEL (priv->model), &iter, chord);
        }

      changed = TRUE;
    }

  gtk_widget_destroy (GTK_WIDGET (dialog));

  if (changed)
    g_signal_emit (self, signals [CHANGED], 0);
}

static gboolean
dzl_shortcut_theme_editor_visible_func (GtkTreeModel *model,
                                        GtkTreeIter  *iter,
                                        gpointer      user_data)
{
  const gchar *text = user_data;
  g_autofree gchar *keywords= NULL;
  GtkTreeIter parent;

  g_assert (GTK_IS_TREE_MODEL (model));
  g_assert (iter != NULL);
  g_assert (text != NULL);

  if (!gtk_tree_model_iter_parent (model, &parent, iter))
    return TRUE;

  gtk_tree_model_get (model, iter,
                      DZL_SHORTCUT_MODEL_COLUMN_KEYWORDS, &keywords,
                      -1);

  /* keywords and text are both casefolded */
  if (strstr (keywords, text) != NULL)
    return TRUE;

  return FALSE;
}

static void
dzl_shortcut_theme_editor_filter_changed (DzlShortcutThemeEditor *self,
                                          GtkSearchEntry         *entry)
{
  DzlShortcutThemeEditorPrivate *priv = dzl_shortcut_theme_editor_get_instance_private (self);
  g_autoptr(GtkTreeModel) filter = NULL;
  const gchar *text;

  g_assert (DZL_IS_SHORTCUT_THEME_EDITOR (self));
  g_assert (GTK_IS_SEARCH_ENTRY (entry));

  filter = gtk_tree_model_filter_new (priv->model, NULL);
  text = gtk_entry_get_text (GTK_ENTRY (entry));

  if (dzl_str_empty0 (text))
    {
      gtk_tree_view_set_model (priv->tree_view, priv->model);
      gtk_tree_view_expand_all (priv->tree_view);
      return;
    }

  gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter),
                                          dzl_shortcut_theme_editor_visible_func,
                                          g_utf8_casefold (text, -1),
                                          g_free);
  gtk_tree_view_set_model (priv->tree_view, GTK_TREE_MODEL (filter));
  gtk_tree_view_expand_all (priv->tree_view);
}

static void
dzl_shortcut_theme_editor_row_activated (DzlShortcutThemeEditor *self,
                                         GtkTreePath            *tree_path,
                                         GtkTreeViewColumn      *column,
                                         GtkTreeView            *tree_view)
{
  DzlShortcutThemeEditorPrivate *priv = dzl_shortcut_theme_editor_get_instance_private (self);
  GtkTreeModel *model;
  GtkTreeIter iter;

  g_assert (DZL_IS_SHORTCUT_THEME_EDITOR (self));
  g_assert (GTK_IS_TREE_VIEW (tree_view));
  g_assert (tree_path != NULL);
  g_assert (GTK_IS_TREE_VIEW_COLUMN (column));

  if (gtk_tree_path_get_depth (tree_path) == 1)
    return;

  model = gtk_tree_view_get_model (tree_view);

  if (gtk_tree_model_get_iter (model, &iter, tree_path))
    {
      g_autofree gchar *title = NULL;
      g_autofree gchar *accel = NULL;
      GtkDialog *dialog;
      GtkWidget *toplevel;

      g_clear_pointer (&priv->selected, gtk_tree_path_free);
      priv->selected = gtk_tree_path_copy (tree_path);

      gtk_tree_model_get (model, &iter,
                          DZL_SHORTCUT_MODEL_COLUMN_TITLE, &title,
                          DZL_SHORTCUT_MODEL_COLUMN_ACCEL, &accel,
                          -1);

      toplevel = gtk_widget_get_ancestor (GTK_WIDGET (self), GTK_TYPE_WINDOW);

      dialog = g_object_new (DZL_TYPE_SHORTCUT_ACCEL_DIALOG,
                             "modal", TRUE,
                             "resizable", FALSE,
                             "shortcut-title", title,
                             "title", _("Set Shortcut"),
                             "transient-for", toplevel,
                             "use-header-bar", TRUE,
                             NULL);

      g_signal_connect_object (dialog,
                               "response",
                               G_CALLBACK (dzl_shortcut_theme_editor_dialog_response),
                               self,
                               G_CONNECT_SWAPPED);

      gtk_window_present (GTK_WINDOW (dialog));
    }
}

static void
shortcut_cell_data_func (GtkCellLayout   *cell_layout,
                         GtkCellRenderer *renderer,
                         GtkTreeModel    *model,
                         GtkTreeIter     *iter,
                         gpointer         user_data)
{
  DzlShortcutThemeEditor *self = user_data;
  g_autofree gchar *accel = NULL;
  GtkTreeIter piter;

  g_assert (GTK_IS_TREE_VIEW_COLUMN (cell_layout));
  g_assert (GTK_IS_CELL_RENDERER (renderer));
  g_assert (GTK_IS_TREE_MODEL (model));
  g_assert (iter != NULL);
  g_assert (DZL_IS_SHORTCUT_THEME_EDITOR (self));

  gtk_tree_model_get (model, iter,
                      DZL_SHORTCUT_MODEL_COLUMN_ACCEL, &accel,
                      -1);

  if (accel && *accel)
    g_object_set (renderer, "text", accel, NULL);
  else if (gtk_tree_model_iter_parent (model, &piter, iter))
    g_object_set (renderer, "text", "Disabled", NULL);
  else
    g_object_set (renderer, "text", NULL, NULL);
}

static void
title_cell_data_func (GtkCellLayout   *cell_layout,
                      GtkCellRenderer *renderer,
                      GtkTreeModel    *model,
                      GtkTreeIter     *iter,
                      gpointer         user_data)
{
  DzlShortcutThemeEditor *self = user_data;
  DzlShortcutThemeEditorPrivate *priv = dzl_shortcut_theme_editor_get_instance_private (self);
  g_autofree gchar *title = NULL;
  GtkTreeIter piter;

  g_assert (GTK_IS_TREE_VIEW_COLUMN (cell_layout));
  g_assert (GTK_IS_CELL_RENDERER (renderer));
  g_assert (GTK_IS_TREE_MODEL (model));
  g_assert (iter != NULL);
  g_assert (DZL_IS_SHORTCUT_THEME_EDITOR (self));

  gtk_tree_model_get (model, iter,
                      DZL_SHORTCUT_MODEL_COLUMN_TITLE, &title,
                      -1);

  g_object_set (renderer, "text", title, NULL);

  if (!gtk_tree_model_iter_parent (model, &piter, iter))
    g_object_set (renderer, "attributes", priv->attrs, NULL);
  else
    g_object_set (renderer, "attributes", NULL, NULL);
}

static void
dzl_shortcut_theme_editor_changed (DzlShortcutThemeEditor *self,
                                   DzlShortcutManager     *manager)
{
  DzlShortcutThemeEditorPrivate *priv = dzl_shortcut_theme_editor_get_instance_private (self);

  g_assert (DZL_IS_SHORTCUT_THEME_EDITOR (self));
  g_assert (DZL_IS_SHORTCUT_MANAGER (manager));

  dzl_shortcut_model_rebuild (DZL_SHORTCUT_MODEL (priv->model));
  gtk_tree_view_expand_all (priv->tree_view);
}

static void
dzl_shortcut_theme_editor_finalize (GObject *object)
{
  DzlShortcutThemeEditor *self = (DzlShortcutThemeEditor *)object;
  DzlShortcutThemeEditorPrivate *priv = dzl_shortcut_theme_editor_get_instance_private (self);

  g_clear_object (&priv->model);
  g_clear_object (&priv->theme);
  g_clear_pointer (&priv->selected, gtk_tree_path_free);
  g_clear_pointer (&priv->attrs, pango_attr_list_unref);

  G_OBJECT_CLASS (dzl_shortcut_theme_editor_parent_class)->finalize (object);
}

static void
dzl_shortcut_theme_editor_get_property (GObject    *object,
                                        guint       prop_id,
                                        GValue     *value,
                                        GParamSpec *pspec)
{
  DzlShortcutThemeEditor *self = DZL_SHORTCUT_THEME_EDITOR (object);

  switch (prop_id)
    {
    case PROP_THEME:
      g_value_set_object (value, dzl_shortcut_theme_editor_get_theme (self));
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    }
}

static void
dzl_shortcut_theme_editor_set_property (GObject      *object,
                                        guint         prop_id,
                                        const GValue *value,
                                        GParamSpec   *pspec)
{
  DzlShortcutThemeEditor *self = DZL_SHORTCUT_THEME_EDITOR (object);

  switch (prop_id)
    {
    case PROP_THEME:
      dzl_shortcut_theme_editor_set_theme (self, g_value_get_object (value));
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    }
}

static void
dzl_shortcut_theme_editor_class_init (DzlShortcutThemeEditorClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);

  object_class->finalize = dzl_shortcut_theme_editor_finalize;
  object_class->get_property = dzl_shortcut_theme_editor_get_property;
  object_class->set_property = dzl_shortcut_theme_editor_set_property;

  properties [PROP_THEME] =
    g_param_spec_object ("theme",
                         "Theme",
                         "The theme for editing",
                         DZL_TYPE_SHORTCUT_THEME,
                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));

  g_object_class_install_properties (object_class, N_PROPS, properties);

  /**
   * DzlShortcutThemeEditor::changed:
   *
   * The "changed" signal is emitted when one of the rows within the editor
   * has been changed.
   *
   * You might want to use this signal to save your theme changes to your
   * configured storage backend.
   */
  signals [CHANGED] =
    g_signal_new ("changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0, NULL, NULL, NULL,
                  G_TYPE_NONE, 0);

  gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/dazzle/ui/dzl-shortcut-theme-editor.ui");

  gtk_widget_class_bind_template_child_private (widget_class, DzlShortcutThemeEditor, tree_view);
  gtk_widget_class_bind_template_child_private (widget_class, DzlShortcutThemeEditor, filter_entry);
  gtk_widget_class_bind_template_child_private (widget_class, DzlShortcutThemeEditor, shortcut_cell);
  gtk_widget_class_bind_template_child_private (widget_class, DzlShortcutThemeEditor, shortcut_column);
  gtk_widget_class_bind_template_child_private (widget_class, DzlShortcutThemeEditor, title_cell);
  gtk_widget_class_bind_template_child_private (widget_class, DzlShortcutThemeEditor, title_column);
}

static void
dzl_shortcut_theme_editor_init (DzlShortcutThemeEditor *self)
{
  DzlShortcutThemeEditorPrivate *priv = dzl_shortcut_theme_editor_get_instance_private (self);
  PangoAttrList *list = NULL;

  gtk_widget_init_template (GTK_WIDGET (self));

  priv->model = dzl_shortcut_model_new ();
  gtk_tree_view_set_model (priv->tree_view, priv->model);

  g_signal_connect_object (dzl_shortcut_manager_get_default (),
                           "changed",
                           G_CALLBACK (dzl_shortcut_theme_editor_changed),
                           self,
                           G_CONNECT_SWAPPED);

  g_signal_connect_object (priv->filter_entry,
                           "changed",
                           G_CALLBACK (dzl_shortcut_theme_editor_filter_changed),
                           self,
                           G_CONNECT_SWAPPED);

  g_signal_connect_object (priv->tree_view,
                           "row-activated",
                           G_CALLBACK (dzl_shortcut_theme_editor_row_activated),
                           self,
                           G_CONNECT_SWAPPED);

  /* Set "dim-label" like alpha on the shortcut label */
  list = pango_attr_list_new ();
  pango_attr_list_insert (list, pango_attr_foreground_alpha_new (0.55 * G_MAXUSHORT));
  g_object_set (priv->shortcut_cell,
                "attributes", list,
                NULL);
  pango_attr_list_unref (list);

  /* Setup cell data funcs */
  gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (priv->title_column),
                                      GTK_CELL_RENDERER (priv->title_cell),
                                      title_cell_data_func,
                                      self,
                                      NULL);
  gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (priv->shortcut_column),
                                      GTK_CELL_RENDERER (priv->shortcut_cell),
                                      shortcut_cell_data_func,
                                      self,
                                      NULL);

  /* diable selections on the treeview */
  gtk_tree_selection_set_mode (gtk_tree_view_get_selection (priv->tree_view),
                               GTK_SELECTION_NONE);

  priv->attrs = pango_attr_list_new ();
  pango_attr_list_insert (priv->attrs, pango_attr_foreground_alpha_new (0.55 * 0xFFFF));
}

GtkWidget *
dzl_shortcut_theme_editor_new (void)
{
  return g_object_new (DZL_TYPE_SHORTCUT_THEME_EDITOR, NULL);
}

/**
 * dzl_shortcut_theme_editor_get_theme:
 * @self: a #DzlShortcutThemeEditor
 *
 * Gets the shortcut theme if one hsa been set.
 *
 * Returns: (transfer none) (nullable): An #DzlShortcutTheme or %NULL
 */
DzlShortcutTheme *
dzl_shortcut_theme_editor_get_theme (DzlShortcutThemeEditor *self)
{
  DzlShortcutThemeEditorPrivate *priv = dzl_shortcut_theme_editor_get_instance_private (self);

  g_return_val_if_fail (DZL_IS_SHORTCUT_THEME_EDITOR (self), NULL);

  return priv->theme;
}

void
dzl_shortcut_theme_editor_set_theme (DzlShortcutThemeEditor *self,
                                     DzlShortcutTheme       *theme)
{
  DzlShortcutThemeEditorPrivate *priv = dzl_shortcut_theme_editor_get_instance_private (self);

  g_return_if_fail (DZL_IS_SHORTCUT_THEME_EDITOR (self));
  g_return_if_fail (!theme || DZL_IS_SHORTCUT_THEME (theme));

  if (g_set_object (&priv->theme, theme))
    {
      dzl_shortcut_model_set_theme (DZL_SHORTCUT_MODEL (priv->model), theme);
      gtk_tree_view_expand_all (priv->tree_view);
      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_THEME]);
    }
}