[gnome-control-center/wip/gbsneto/new-keyboard-panel: 11/16] keyboard: bring back uniqueness check



commit 5d23a69e33151b92dca90f57c98b24f00d1a0a2d
Author: Georges Basile Stavracas Neto <georges stavracas gmail com>
Date:   Wed Jul 13 20:09:17 2016 -0300

    keyboard: bring back uniqueness check
    
    The collision detection code was removed as the
    cleanup was happening, and this patch readd the
    feature in a much cleaner and saner code.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=769063

 panels/keyboard/cc-keyboard-manager.c         |  122 +++++++++++++++++++++++++
 panels/keyboard/cc-keyboard-manager.h         |    9 ++
 panels/keyboard/cc-keyboard-shortcut-editor.c |   95 +++++++++++++++++++-
 panels/keyboard/shortcut-editor.ui            |   13 +++
 4 files changed, 238 insertions(+), 1 deletions(-)
---
diff --git a/panels/keyboard/cc-keyboard-manager.c b/panels/keyboard/cc-keyboard-manager.c
index 7837c72..b6c86b1 100644
--- a/panels/keyboard/cc-keyboard-manager.c
+++ b/panels/keyboard/cc-keyboard-manager.c
@@ -84,6 +84,62 @@ free_key_array (GPtrArray *keys)
     }
 }
 
+static gboolean
+compare_keys_for_uniqueness (CcKeyboardItem   *current_item,
+                             CcUniquenessData *data)
+{
+  CcKeyboardItem *item;
+
+  item = data->orig_item;
+
+  /* No conflict for: blanks, different modifiers or ourselves */
+  if (!current_item ||
+      item == current_item ||
+      data->new_mask != current_item->mask)
+    {
+      return FALSE;
+    }
+
+  if (item && cc_keyboard_item_equal (item, current_item))
+    goto out;
+
+  if (data->new_keyval != 0)
+    {
+      if (data->new_keyval != current_item->keyval)
+          return FALSE;
+    }
+  else if (current_item->keyval != 0 || data->new_keycode != current_item->keycode)
+    {
+      return FALSE;
+    }
+
+out:
+  data->conflict_item = current_item;
+
+  return TRUE;
+}
+
+static gboolean
+check_for_uniqueness (gpointer          key,
+                      GPtrArray        *keys_array,
+                      CcUniquenessData *data)
+{
+  guint i;
+
+  for (i = 0; i < keys_array->len; i++)
+    {
+      CcKeyboardItem *item;
+
+      item = keys_array->pdata[i];
+
+      if (compare_keys_for_uniqueness (item, data))
+        return TRUE;
+    }
+
+  return FALSE;
+}
+
+
 static GHashTable*
 get_hash_for_group (CcKeyboardManager *self,
                     BindingGroupType   group)
@@ -845,3 +901,69 @@ cc_keyboard_manager_remove_custom_shortcut  (CcKeyboardManager *self,
 
   g_signal_emit (self, signals[SHORTCUT_REMOVED], 0, item);
 }
+
+/**
+ * cc_keyboard_manager_get_collision:
+ * @self: a #CcKeyboardManager
+ * @item: (nullable): a keyboard shortcut
+ * @keyval: the key value
+ * @mask: a mask for the key sequence
+ * @keycode: the code of the key.
+ *
+ * Retrieves the collision item for the given shortcut.
+ *
+ * Returns: (transfer none)(nullable): the collisioned shortcut
+ */
+CcKeyboardItem*
+cc_keyboard_manager_get_collision (CcKeyboardManager *self,
+                                   CcKeyboardItem    *item,
+                                   gint               keyval,
+                                   GdkModifierType    mask,
+                                   gint               keycode)
+{
+  CcUniquenessData data;
+
+  g_return_val_if_fail (CC_IS_KEYBOARD_MANAGER (self), NULL);
+
+  data.orig_item = item;
+  data.new_keyval = keyval;
+  data.new_mask = mask;
+  data.new_keycode = keycode;
+  data.conflict_item = NULL;
+
+  /* Any number of shortcuts can be disabled */
+  if (keyval != 0 || keycode != 0)
+    {
+      BindingGroupType i;
+
+      for (i = BINDING_GROUP_SYSTEM; i <= BINDING_GROUP_USER && !data.conflict_item; i++)
+        {
+          GHashTable *table;
+
+          table = get_hash_for_group (self, i);
+
+          if (!table)
+            continue;
+
+          g_hash_table_find (table, (GHRFunc) check_for_uniqueness, &data);
+        }
+    }
+
+  return data.conflict_item;
+}
+
+/**
+ * cc_keyboard_manager_disable_shortcut:
+ * @self: a #CcKeyboardManager
+ * @item: a @CcKeyboardItem
+ *
+ * Disables the given keyboard shortcut.
+ */
+void
+cc_keyboard_manager_disable_shortcut (CcKeyboardManager *self,
+                                      CcKeyboardItem    *item)
+{
+  g_return_if_fail (CC_IS_KEYBOARD_MANAGER (self));
+
+  g_object_set (item, "binding", NULL, NULL);
+}
diff --git a/panels/keyboard/cc-keyboard-manager.h b/panels/keyboard/cc-keyboard-manager.h
index ab14d4a..b63ffa5 100644
--- a/panels/keyboard/cc-keyboard-manager.h
+++ b/panels/keyboard/cc-keyboard-manager.h
@@ -45,6 +45,15 @@ void                 cc_keyboard_manager_add_custom_shortcut     (CcKeyboardMana
 void                 cc_keyboard_manager_remove_custom_shortcut  (CcKeyboardManager  *self,
                                                                   CcKeyboardItem     *item);
 
+CcKeyboardItem*      cc_keyboard_manager_get_collision           (CcKeyboardManager  *self,
+                                                                  CcKeyboardItem     *item,
+                                                                  gint                keyval,
+                                                                  GdkModifierType     mask,
+                                                                  gint                keycode);
+
+void                 cc_keyboard_manager_disable_shortcut        (CcKeyboardManager  *self,
+                                                                  CcKeyboardItem     *item);
+
 G_END_DECLS
 
 #endif /* CC_KEYBOARD_MANAGER_H */
diff --git a/panels/keyboard/cc-keyboard-shortcut-editor.c b/panels/keyboard/cc-keyboard-shortcut-editor.c
index 74b1911..2b3724b 100644
--- a/panels/keyboard/cc-keyboard-shortcut-editor.c
+++ b/panels/keyboard/cc-keyboard-shortcut-editor.c
@@ -45,9 +45,11 @@ struct _CcKeyboardShortcutEditor
   GtkWidget          *edit_button;
   GtkWidget          *headerbar;
   GtkWidget          *name_entry;
+  GtkWidget          *new_shortcut_conflict_label;
   GtkWidget          *remove_button;
   GtkWidget          *replace_button;
   GtkWidget          *shortcut_accel_label;
+  GtkWidget          *shortcut_conflict_label;
   GtkWidget          *stack;
   GtkWidget          *top_info_label;
 
@@ -58,6 +60,8 @@ struct _CcKeyboardShortcutEditor
   CcKeyboardManager  *manager;
   CcKeyboardItem     *item;
 
+  CcKeyboardItem     *collision_item;
+
   /* Custom shortcuts */
   GdkDevice          *grab_pointer;
 
@@ -124,6 +128,8 @@ clear_custom_entries (CcKeyboardShortcutEditor *self)
   gtk_entry_set_text (GTK_ENTRY (self->command_entry), "");
 
   cc_shortcut_label_set_accelerator (CC_SHORTCUT_LABEL (self->custom_shortcut_accel_label), "");
+  gtk_label_set_label (GTK_LABEL (self->new_shortcut_conflict_label), "");
+  gtk_label_set_label (GTK_LABEL (self->shortcut_conflict_label), "");
 
   self->custom_keycode = 0;
   self->custom_keyval = 0;
@@ -131,6 +137,8 @@ clear_custom_entries (CcKeyboardShortcutEditor *self)
   self->custom_is_modifier = TRUE;
   self->edited = FALSE;
 
+  self->collision_item = NULL;
+
   g_signal_handlers_unblock_by_func (self->command_entry, command_entry_changed_cb, self);
   g_signal_handlers_unblock_by_func (self->name_entry, name_entry_changed_cb, self);
 }
@@ -202,6 +210,10 @@ update_shortcut (CcKeyboardShortcutEditor *self)
   /* Setup the binding */
   apply_custom_item_fields (self, self->item);
 
+  /* Eventually disable the conflict shortcut */
+  if (self->collision_item)
+    cc_keyboard_manager_disable_shortcut (self->manager, self->collision_item);
+
   /* Cleanup whatever was set before */
   clear_custom_entries (self);
 
@@ -218,6 +230,16 @@ get_current_shortcut_label (CcKeyboardShortcutEditor *self)
 }
 
 static void
+set_collision_headerbar (CcKeyboardShortcutEditor *self,
+                         gboolean                  has_collision)
+{
+  gtk_header_bar_set_show_close_button (GTK_HEADER_BAR (self->headerbar), !has_collision);
+
+  gtk_widget_set_visible (self->cancel_button, has_collision);
+  gtk_widget_set_visible (self->replace_button, has_collision);
+}
+
+static void
 validate_custom_shortcut (CcKeyboardShortcutEditor *self)
 {
   gboolean valid;
@@ -239,9 +261,17 @@ validate_custom_shortcut (CcKeyboardShortcutEditor *self)
   if (valid)
     {
       CcShortcutLabel *shortcut_label;
+      CcKeyboardItem *collision_item;
       gchar *accel;
 
       shortcut_label = get_current_shortcut_label (self);
+
+      collision_item = cc_keyboard_manager_get_collision (self->manager,
+                                                          self->item,
+                                                          self->custom_keyval,
+                                                          self->custom_mask,
+                                                          self->custom_keycode);
+
       accel = gtk_accelerator_name (self->custom_keyval, self->custom_mask);
 
       /* Setup the accelerator label */
@@ -257,6 +287,48 @@ validate_custom_shortcut (CcKeyboardShortcutEditor *self)
 
       release_grab (self);
 
+      /*
+       * Oops! Looks like the accelerator is already being used, so we
+       * must warn the user and let it be very clear that adding this
+       * shortcut will disable the other.
+       */
+      gtk_widget_set_visible (self->new_shortcut_conflict_label, collision_item != NULL);
+
+      if (collision_item)
+        {
+          GtkWidget *label;
+          gchar *friendly_accelerator;
+          gchar *collision_text;
+
+          friendly_accelerator = convert_keysym_state_to_string (self->custom_keyval,
+                                                                 self->custom_mask,
+                                                                 self->custom_keycode);
+
+          collision_text = g_strdup_printf (_("%s is already being used for <b>%s</b>. If you "
+                                              "replace it, %s will be disabled"),
+                                            friendly_accelerator,
+                                            collision_item->description,
+                                            collision_item->description);
+
+          label = is_custom_shortcut (self) ? self->new_shortcut_conflict_label : 
self->shortcut_conflict_label;
+
+          gtk_label_set_markup (GTK_LABEL (label), collision_text);
+
+          g_free (friendly_accelerator);
+          g_free (collision_text);
+        }
+
+      /*
+       * When there is a collision between the current shortcut and another shortcut,
+       * and we're editing an existing shortcut (rather than creating a new one), setup
+       * the headerbar to display "Cancel" and "Replace". Otherwise, make sure to set
+       * only the close button again.
+       */
+      if (self->mode == CC_SHORTCUT_EDITOR_EDIT)
+        set_collision_headerbar (self, collision_item != NULL);
+
+      self->collision_item = collision_item;
+
       g_free (accel);
     }
 }
@@ -271,6 +343,10 @@ add_button_clicked_cb (CcKeyboardShortcutEditor *self)
   /* Apply the custom shortcut setup at the new item */
   apply_custom_item_fields (self, item);
 
+  /* Eventually disable the conflict shortcut */
+  if (self->collision_item)
+    cc_keyboard_manager_disable_shortcut (self->manager, self->collision_item);
+
   /* Cleanup everything once we're done */
   clear_custom_entries (self);
 
@@ -321,6 +397,14 @@ remove_button_clicked_cb (CcKeyboardShortcutEditor *self)
 }
 
 static void
+replace_button_clicked_cb (CcKeyboardShortcutEditor *self)
+{
+  update_shortcut (self);
+
+  gtk_widget_hide (GTK_WIDGET (self));
+}
+
+static void
 setup_keyboard_item (CcKeyboardShortcutEditor *self,
                      CcKeyboardItem           *item)
 {
@@ -581,9 +665,11 @@ cc_keyboard_shortcut_editor_class_init (CcKeyboardShortcutEditorClass *klass)
   gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, edit_button);
   gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, headerbar);
   gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, name_entry);
+  gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, new_shortcut_conflict_label);
   gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, remove_button);
   gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, replace_button);
   gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, shortcut_accel_label);
+  gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, shortcut_conflict_label);
   gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, stack);
   gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, top_info_label);
 
@@ -593,6 +679,7 @@ cc_keyboard_shortcut_editor_class_init (CcKeyboardShortcutEditorClass *klass)
   gtk_widget_class_bind_template_callback (widget_class, edit_custom_shortcut_button_toggled_cb);
   gtk_widget_class_bind_template_callback (widget_class, name_entry_changed_cb);
   gtk_widget_class_bind_template_callback (widget_class, remove_button_clicked_cb);
+  gtk_widget_class_bind_template_callback (widget_class, replace_button_clicked_cb);
 }
 
 static void
@@ -673,9 +760,15 @@ cc_keyboard_shortcut_editor_set_mode (CcKeyboardShortcutEditor *self,
 
   if (self->mode != mode)
     {
+      gboolean is_create_mode;
+
       self->mode = mode;
 
-      if (mode == CC_SHORTCUT_EDITOR_CREATE)
+      is_create_mode = mode == CC_SHORTCUT_EDITOR_CREATE;
+
+      gtk_widget_set_visible (self->new_shortcut_conflict_label, is_create_mode);
+
+      if (is_create_mode)
         {
           /* Cleanup whatever was set before */
           clear_custom_entries (self);
diff --git a/panels/keyboard/shortcut-editor.ui b/panels/keyboard/shortcut-editor.ui
index b1e04c5..8978461 100644
--- a/panels/keyboard/shortcut-editor.ui
+++ b/panels/keyboard/shortcut-editor.ui
@@ -46,6 +46,18 @@
                   </object>
                 </child>
                 <child>
+                  <object class="GtkLabel" id="shortcut_conflict_label">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="hexpand">True</property>
+                    <property name="wrap">True</property>
+                    <property name="wrap_mode">word-char</property>
+                    <property name="width_chars">15</property>
+                    <property name="max_width_chars">20</property>
+                    <property name="xalign">0</property>
+                  </object>
+                </child>
+                <child>
                   <object class="GtkButton" id="reset_button">
                     <property name="label" translatable="yes">Reset</property>
                     <property name="visible">True</property>
@@ -242,6 +254,7 @@
             <property name="visible">True</property>
             <property name="can_focus">True</property>
             <property name="receives_default">True</property>
+            <signal name="clicked" handler="replace_button_clicked_cb" object="CcKeyboardShortcutEditor" 
swapped="yes" />
           </object>
           <packing>
             <property name="pack_type">end</property>


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]