From aba5d3baeac78a5bcc41bd871cb506e3b0a48b96 Mon Sep 17 00:00:00 2001
From: Alexander Schwinn <alexxcons@xfce.org>
Date: Sun, 17 May 2020 02:36:26 +0200
Subject: [PATCH] Introduced widget thunar-menu in order to unify the way menus
 are build in thunar, and used it for the context menu (Issue #293)

---
 po/POTFILES.in                |   1 +
 thunar/Makefile.am            |   2 +
 thunar/thunar-menu.c          | 347 ++++++++++++++++++++++++++++++++++
 thunar/thunar-menu.h          |  74 ++++++++
 thunar/thunar-standard-view.c |  45 ++++-
 5 files changed, 461 insertions(+), 8 deletions(-)
 create mode 100644 thunar/thunar-menu.c
 create mode 100644 thunar/thunar-menu.h

diff --git a/po/POTFILES.in b/po/POTFILES.in
index f97790c1d..01566fb82 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -45,6 +45,7 @@ thunar/thunar-location-bar.c
 thunar/thunar-location-button.c
 thunar/thunar-location-buttons.c
 thunar/thunar-location-entry.c
+thunar/thunar-menu.c
 thunar/thunar-notify.c
 thunar/thunar-navigator.c
 thunar/thunar-pango-extensions.c
diff --git a/thunar/Makefile.am b/thunar/Makefile.am
index 428cf4a43..38d701040 100644
--- a/thunar/Makefile.am
+++ b/thunar/Makefile.am
@@ -140,6 +140,8 @@ thunar_SOURCES =							\
 	thunar-location-buttons.h					\
 	thunar-location-entry.c						\
 	thunar-location-entry.h						\
+	thunar-menu.c							\
+	thunar-menu.h							\
 	thunar-menu-util.c						\
 	thunar-menu-util.h						\
 	thunar-notify.c							\
diff --git a/thunar/thunar-menu.c b/thunar/thunar-menu.c
new file mode 100644
index 000000000..5271d049d
--- /dev/null
+++ b/thunar/thunar-menu.c
@@ -0,0 +1,347 @@
+/*-
+ * Copyright (c) 2020 Alexander Schwinn <alexxcons@xfce.org>
+ *
+ * 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 Foundation, Inc., 59 Temple
+ * Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <thunar/thunar-menu.h>
+
+#include <thunar/thunar-gtk-extensions.h>
+#include <thunar/thunar-launcher.h>
+#include <thunar/thunar-private.h>
+#include <thunar/thunar-window.h>
+
+/* property identifiers */
+enum
+{
+  PROP_0,
+  PROP_MENU_TYPE,
+  PROP_LAUNCHER,
+  PROP_FORCE_SECTION_OPEN,
+  PROP_TAB_SUPPORT_DISABLED,
+  PROP_CHANGE_DIRECTORY_SUPPORT_DISABLED,
+};
+
+static void thunar_menu_finalize      (GObject                *object);
+static void thunar_menu_get_property  (GObject                *object,
+                                       guint                   prop_id,
+                                       GValue                 *value,
+                                       GParamSpec             *pspec);
+static void thunar_menu_set_property  (GObject                *object,
+                                       guint                   prop_uid,
+                                       const GValue           *value,
+                                       GParamSpec             *pspec);
+
+struct _ThunarMenuClass
+{
+  GtkMenuClass __parent__;
+};
+
+struct _ThunarMenu
+{
+  GtkMenu __parent__;
+  ThunarLauncher  *launcher;
+
+  /* true, if the 'open' section should be forced */
+  gboolean         force_section_open;
+
+  /* true, if 'open as new tab' should not be shown */
+  gboolean         tab_support_disabled;
+
+  /* true, if 'open' for folders, which would result in changing the directory, should not be shown */
+  gboolean         change_directory_support_disabled;
+
+  /* detailed type of the thunar menu */
+  ThunarMenuType   type;
+};
+
+
+
+static GQuark thunar_menu_handler_quark;
+
+G_DEFINE_TYPE (ThunarMenu, thunar_menu, GTK_TYPE_MENU)
+
+
+
+static void
+thunar_menu_class_init (ThunarMenuClass *klass)
+{
+  GObjectClass *gobject_class;
+
+  /* determine the "thunar-menu-handler" quark */
+  thunar_menu_handler_quark = g_quark_from_static_string ("thunar-menu-handler");
+
+  gobject_class = G_OBJECT_CLASS (klass);
+
+  gobject_class->finalize = thunar_menu_finalize;
+  gobject_class->get_property = thunar_menu_get_property;
+  gobject_class->set_property = thunar_menu_set_property;
+
+  g_object_class_install_property (gobject_class,
+                                   PROP_MENU_TYPE,
+                                   g_param_spec_int ("menu-type",
+                                                     "menu-type",
+                                                     "menu-type",
+                                                     0, 1, 0, // min, max, default
+                                                     G_PARAM_WRITABLE
+                                                     | G_PARAM_CONSTRUCT_ONLY));
+
+  g_object_class_install_property (gobject_class,
+                                   PROP_LAUNCHER,
+                                   g_param_spec_object ("launcher",
+                                                        "launcher",
+                                                        "launcher",
+                                                        THUNAR_TYPE_LAUNCHER,
+                                                          G_PARAM_WRITABLE
+                                                        | G_PARAM_CONSTRUCT_ONLY));
+
+  g_object_class_install_property (gobject_class,
+                                   PROP_FORCE_SECTION_OPEN,
+                                   g_param_spec_boolean ("force-section-open",
+                                                         "force-section-open",
+                                                         "force-section-open",
+                                                         FALSE,
+                                                           G_PARAM_WRITABLE
+                                                         | G_PARAM_CONSTRUCT_ONLY));
+
+  g_object_class_install_property (gobject_class,
+                                   PROP_TAB_SUPPORT_DISABLED,
+                                   g_param_spec_boolean ("tab-support-disabled",
+                                                         "tab-support-disabled",
+                                                         "tab-support-disabled",
+                                                         FALSE,
+                                                           G_PARAM_WRITABLE
+                                                         | G_PARAM_CONSTRUCT_ONLY));
+
+  g_object_class_install_property (gobject_class,
+                                   PROP_CHANGE_DIRECTORY_SUPPORT_DISABLED,
+                                   g_param_spec_boolean ("change_directory-support-disabled",
+                                                         "change_directory-support-disabled",
+                                                         "change_directory-support-disabled",
+                                                         FALSE,
+                                                           G_PARAM_WRITABLE
+                                                         | G_PARAM_CONSTRUCT_ONLY));
+}
+
+
+
+static void
+thunar_menu_init (ThunarMenu *menu)
+{
+  menu->force_section_open = FALSE;
+  menu->type = FALSE;
+  menu->tab_support_disabled = FALSE;
+  menu->change_directory_support_disabled = FALSE;
+}
+
+
+
+static void
+thunar_menu_finalize (GObject *object)
+{
+  ThunarMenu *menu = THUNAR_MENU (object);
+
+  g_object_unref (menu->launcher);
+
+  (*G_OBJECT_CLASS (thunar_menu_parent_class)->finalize) (object);
+}
+
+
+
+static void
+thunar_menu_get_property (GObject    *object,
+                          guint       prop_id,
+                          GValue     *value,
+                          GParamSpec *pspec)
+{
+  switch (prop_id)
+    {
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+
+
+static void
+thunar_menu_set_property (GObject      *object,
+                          guint         prop_id,
+                          const GValue *value,
+                          GParamSpec   *pspec)
+{
+  ThunarMenu *menu = THUNAR_MENU (object);
+
+  switch (prop_id)
+    {
+    case PROP_MENU_TYPE:
+      menu->type = g_value_get_int (value);
+      break;
+
+    case PROP_LAUNCHER:
+      menu->launcher = g_value_dup_object (value);
+      g_object_ref (G_OBJECT (menu->launcher));
+     break;
+
+    case PROP_FORCE_SECTION_OPEN:
+      menu->force_section_open = g_value_get_boolean (value);
+      break;
+
+    case PROP_TAB_SUPPORT_DISABLED:
+      menu->tab_support_disabled = g_value_get_boolean (value);
+      break;
+
+    case PROP_CHANGE_DIRECTORY_SUPPORT_DISABLED:
+      menu->change_directory_support_disabled = g_value_get_boolean (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+
+
+/**
+ * thunar_menu_add_sections:
+ * @menu : a #ThunarMenu instance
+ * @menu_sections : bit enumeration of #ThunarMenuSections which should be added to the #ThunarMenu
+ *
+ * Method to add different sections of #GtkMenuItems to the #ThunarMenu,
+ * according to the selected #ThunarMenuSections
+ *
+ * Return value: TRUE if any #GtkMenuItem was added
+ **/
+gboolean
+thunar_menu_add_sections (ThunarMenu         *menu,
+                          ThunarMenuSections  menu_sections)
+{
+  GtkWidget *window;
+  gboolean   item_added;
+  gboolean   is_window_menu = menu->type == THUNAR_MENU_TYPE_WINDOW;
+
+  _thunar_return_val_if_fail (THUNAR_IS_MENU (menu), FALSE);
+
+  if (menu_sections & THUNAR_MENU_SECTION_OPEN)
+    {
+      if (thunar_launcher_append_open_section (menu->launcher, GTK_MENU_SHELL (menu), !menu->tab_support_disabled, !menu->change_directory_support_disabled, menu->force_section_open))
+         xfce_gtk_menu_append_seperator (GTK_MENU_SHELL (menu));
+    }
+
+  if (menu_sections & THUNAR_MENU_SECTION_SENDTO)
+    {
+      item_added = thunar_launcher_append_menu_item (menu->launcher, GTK_MENU_SHELL (menu), THUNAR_LAUNCHER_ACTION_SENDTO_MENU, FALSE) != NULL;
+      if (item_added)
+         xfce_gtk_menu_append_seperator (GTK_MENU_SHELL (menu));
+    }
+  if (menu_sections & THUNAR_MENU_SECTION_CREATE_NEW_FILES)
+    {
+      item_added = FALSE;
+      item_added |= (thunar_launcher_append_menu_item (menu->launcher, GTK_MENU_SHELL (menu), THUNAR_LAUNCHER_ACTION_CREATE_FOLDER, is_window_menu) != NULL);
+      // TODO: Create Document
+      if (item_added)
+         xfce_gtk_menu_append_seperator (GTK_MENU_SHELL (menu));
+    }
+  item_added = FALSE;
+  if (menu_sections & THUNAR_MENU_SECTION_CUT)
+    item_added |= (thunar_launcher_append_menu_item (menu->launcher, GTK_MENU_SHELL (menu), THUNAR_LAUNCHER_ACTION_CUT, is_window_menu) != NULL);
+  if (menu_sections & THUNAR_MENU_SECTION_COPY_PASTE)
+    {
+      item_added |= (thunar_launcher_append_menu_item (menu->launcher, GTK_MENU_SHELL (menu), THUNAR_LAUNCHER_ACTION_COPY, is_window_menu) != NULL);
+      item_added |= (thunar_launcher_append_menu_item (menu->launcher, GTK_MENU_SHELL (menu), THUNAR_LAUNCHER_ACTION_PASTE, is_window_menu) != NULL);
+    }
+  if (item_added)
+     xfce_gtk_menu_append_seperator (GTK_MENU_SHELL (menu));
+
+  if (menu_sections & THUNAR_MENU_SECTION_TRASH_DELETE)
+    {
+      item_added = FALSE;
+      item_added |= (thunar_launcher_append_menu_item (menu->launcher, GTK_MENU_SHELL (menu), THUNAR_LAUNCHER_ACTION_MOVE_TO_TRASH, is_window_menu) != NULL);
+      item_added |= (thunar_launcher_append_menu_item (menu->launcher, GTK_MENU_SHELL (menu), THUNAR_LAUNCHER_ACTION_DELETE, is_window_menu) != NULL);
+      if (item_added)
+         xfce_gtk_menu_append_seperator (GTK_MENU_SHELL (menu));
+    }
+  if (menu_sections & THUNAR_MENU_SECTION_EMPTY_TRASH)
+    {
+      if (thunar_launcher_append_menu_item (menu->launcher, GTK_MENU_SHELL (menu), THUNAR_LAUNCHER_ACTION_EMPTY_TRASH, FALSE) != NULL )
+         xfce_gtk_menu_append_seperator (GTK_MENU_SHELL (menu));
+    }
+  if (menu_sections & THUNAR_MENU_SECTION_RESTORE)
+    {
+      if (thunar_launcher_append_menu_item (menu->launcher, GTK_MENU_SHELL (menu), THUNAR_LAUNCHER_ACTION_RESTORE, FALSE) != NULL)
+         xfce_gtk_menu_append_seperator (GTK_MENU_SHELL (menu));
+    }
+
+  item_added = FALSE;
+  if (menu_sections & THUNAR_MENU_SECTION_DUPLICATE)
+    item_added |= (thunar_launcher_append_menu_item (menu->launcher, GTK_MENU_SHELL (menu), THUNAR_LAUNCHER_ACTION_DUPLICATE, is_window_menu) != NULL);
+  if (menu_sections & THUNAR_MENU_SECTION_MAKELINK)
+    item_added |= (thunar_launcher_append_menu_item (menu->launcher, GTK_MENU_SHELL (menu), THUNAR_LAUNCHER_ACTION_MAKE_LINK, is_window_menu) != NULL);
+  if (menu_sections & THUNAR_MENU_SECTION_RENAME)
+    item_added |= (thunar_launcher_append_menu_item (menu->launcher, GTK_MENU_SHELL (menu), THUNAR_LAUNCHER_ACTION_RENAME, is_window_menu) != NULL);
+  if (item_added)
+     xfce_gtk_menu_append_seperator (GTK_MENU_SHELL (menu));
+
+  if (menu_sections & THUNAR_MENU_SECTION_CUSTOM_ACTIONS)
+    {
+       // TODO
+    }
+
+  if (menu_sections & THUNAR_MENU_SECTION_ZOOM)
+    {
+       // TODO
+    }
+
+  if (menu_sections & THUNAR_MENU_SECTION_PROPERTIES)
+      thunar_launcher_append_menu_item (menu->launcher, GTK_MENU_SHELL (menu), THUNAR_LAUNCHER_ACTION_PROPERTIES, FALSE);
+
+  return TRUE;
+}
+
+
+
+/**
+ * thunar_menu_get_launcher:
+ * @menu : a #ThunarMenu instance
+ *
+ * Return value: (transfer none): The launcher of this #ThunarMenu instance
+ **/
+GtkWidget*
+thunar_menu_get_launcher (ThunarMenu *menu)
+{
+  _thunar_return_val_if_fail (THUNAR_IS_MENU (menu), NULL);
+  return GTK_WIDGET (menu->launcher);
+}
+
+
+
+/**
+ * thunar_menu_hide_accel_labels:
+ * @menu : a #ThunarMenu instance
+ *
+ * Will hide the accel_labels of all menu items of this menu
+ **/
+void
+thunar_menu_hide_accel_labels (ThunarMenu *menu)
+{
+  _thunar_return_if_fail (THUNAR_IS_MENU (menu));
+
+  for (GList* lp = gtk_container_get_children (GTK_CONTAINER (menu)); lp != NULL; lp = lp->next)
+    xfce_gtk_menu_item_set_accel_label (lp->data, NULL);
+}
diff --git a/thunar/thunar-menu.h b/thunar/thunar-menu.h
new file mode 100644
index 000000000..162725927
--- /dev/null
+++ b/thunar/thunar-menu.h
@@ -0,0 +1,74 @@
+/*-
+ * Copyright (c) 2020 Alexander Schwinn <alexxcons@xfce.org>
+ *
+ * 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 Foundation, Inc., 59 Temple
+ * Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#ifndef __THUNAR_MENU_H__
+#define __THUNAR_MENU_H__
+
+#include <gtk/gtk.h>
+
+#include <thunar/thunar-file.h>
+
+G_BEGIN_DECLS;
+
+typedef struct _ThunarMenuClass ThunarMenuClass;
+typedef struct _ThunarMenu      ThunarMenu;
+
+#define THUNAR_TYPE_MENU             (thunar_menu_get_type ())
+#define THUNAR_MENU(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), THUNAR_TYPE_MENU, ThunarMenu))
+#define THUNAR_MENU_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass),  THUNAR_TYPE_MENU, ThunarMenuClass))
+#define THUNAR_IS_MENU(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), THUNAR_TYPE_MENU))
+#define THUNAR_IS_MENU_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass),  THUNAR_TYPE_MENU))
+#define THUNAR_MENU_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj),  THUNAR_TYPE_MENU, ThunarMenu))
+
+/* For window menu, some items are shown insensitive, instead of hidden */
+typedef enum
+{
+  THUNAR_MENU_TYPE_WINDOW,
+  THUNAR_MENU_TYPE_CONTEXT,
+} ThunarMenuType;
+
+/* Bundles of #GtkMenuItems, which can be created by this widget */
+typedef enum
+{
+  THUNAR_MENU_SECTION_OPEN             = 1 << 0,
+  THUNAR_MENU_SECTION_SENDTO           = 1 << 1,
+  THUNAR_MENU_SECTION_CREATE_NEW_FILES = 1 << 2,
+  THUNAR_MENU_SECTION_CUT              = 1 << 3,
+  THUNAR_MENU_SECTION_COPY_PASTE       = 1 << 4,
+  THUNAR_MENU_SECTION_TRASH_DELETE     = 1 << 5,
+  THUNAR_MENU_SECTION_EMPTY_TRASH      = 1 << 6,
+  THUNAR_MENU_SECTION_RESTORE          = 1 << 7,
+  THUNAR_MENU_SECTION_DUPLICATE        = 1 << 8,
+  THUNAR_MENU_SECTION_MAKELINK         = 1 << 9,
+  THUNAR_MENU_SECTION_RENAME           = 1 << 10,
+  THUNAR_MENU_SECTION_CUSTOM_ACTIONS   = 1 << 11,
+  THUNAR_MENU_SECTION_ZOOM             = 1 << 12,
+  THUNAR_MENU_SECTION_PROPERTIES       = 1 << 13,
+
+} ThunarMenuSections;
+
+
+GType      thunar_menu_get_type          (void) G_GNUC_CONST;
+
+gboolean   thunar_menu_add_sections      (ThunarMenu         *menu,
+                                          ThunarMenuSections  menu_sections);
+GtkWidget* thunar_menu_get_launcher      (ThunarMenu         *menu);
+void       thunar_menu_hide_accel_labels (ThunarMenu         *menu);
+
+G_END_DECLS;
+
+#endif /* !__THUNAR_CONTEXT_MENU_H__ */
diff --git a/thunar/thunar-standard-view.c b/thunar/thunar-standard-view.c
index 58ea6de5b..95ede0e85 100644
--- a/thunar/thunar-standard-view.c
+++ b/thunar/thunar-standard-view.c
@@ -33,6 +33,7 @@
 #include <gdk/gdkkeysyms.h>
 
 #include <thunar/thunar-application.h>
+#include <thunar/thunar-menu.h>
 #include <thunar/thunar-dialogs.h>
 #include <thunar/thunar-dnd.h>
 #include <thunar/thunar-enum-types.h>
@@ -3588,28 +3589,56 @@ thunar_standard_view_size_allocate (ThunarStandardView *standard_view,
 void
 thunar_standard_view_context_menu (ThunarStandardView *standard_view)
 {
-  GtkWidget *menu;
-  GList     *selected_items;
+  GtkWidget  *window;
+  ThunarMenu *context_menu;
+  GList      *selected_items;
 
   _thunar_return_if_fail (THUNAR_IS_STANDARD_VIEW (standard_view));
 
   /* grab an additional reference on the view */
   g_object_ref (G_OBJECT (standard_view));
 
-  /* run the menu (figuring out whether to use the file or the folder context menu) */
   selected_items = (*THUNAR_STANDARD_VIEW_GET_CLASS (standard_view)->get_selected_items) (standard_view);
-G_GNUC_BEGIN_IGNORE_DEPRECATIONS
-  menu = gtk_ui_manager_get_widget (standard_view->ui_manager, (selected_items != NULL) ? "/file-context-menu" : "/folder-context-menu");
-G_GNUC_END_IGNORE_DEPRECATIONS
+
+  window = gtk_widget_get_toplevel (GTK_WIDGET (standard_view));
+
+  context_menu = g_object_new (THUNAR_TYPE_MENU, "menu-type", THUNAR_MENU_TYPE_CONTEXT,
+                                                 "launcher", thunar_window_get_launcher (THUNAR_WINDOW (window)), NULL);
+  if (selected_items != NULL)
+    {
+      thunar_menu_add_sections (context_menu, THUNAR_MENU_SECTION_OPEN
+                                            | THUNAR_MENU_SECTION_SENDTO
+                                            | THUNAR_MENU_SECTION_CUT
+                                            | THUNAR_MENU_SECTION_COPY_PASTE
+                                            | THUNAR_MENU_SECTION_TRASH_DELETE
+                                            | THUNAR_MENU_SECTION_EMPTY_TRASH
+                                            | THUNAR_MENU_SECTION_RESTORE
+                                            | THUNAR_MENU_SECTION_RENAME
+                                            | THUNAR_MENU_SECTION_CUSTOM_ACTIONS
+                                            | THUNAR_MENU_SECTION_PROPERTIES);
+    }
+  else /* right click on some empty space */
+    {
+      thunar_menu_add_sections (context_menu, THUNAR_MENU_SECTION_CREATE_NEW_FILES
+                                            | THUNAR_MENU_SECTION_COPY_PASTE
+                                            | THUNAR_MENU_SECTION_EMPTY_TRASH
+                                            | THUNAR_MENU_SECTION_CUSTOM_ACTIONS);
+      xfce_gtk_menu_append_seperator (GTK_MENU_SHELL (context_menu));
+      thunar_menu_add_sections (context_menu, THUNAR_MENU_SECTION_ZOOM
+                                            | THUNAR_MENU_SECTION_PROPERTIES);
+    }
+  thunar_menu_hide_accel_labels (context_menu);
+  gtk_widget_show_all (GTK_WIDGET (context_menu));
+
   /* if there is a drag_timer_event (long press), we use it */
   if (standard_view->priv->drag_timer_event != NULL)
     {
-      thunar_gtk_menu_run_at_event (GTK_MENU (menu), standard_view->priv->drag_timer_event);
+      thunar_gtk_menu_run_at_event (GTK_MENU (context_menu), standard_view->priv->drag_timer_event);
       gdk_event_free (standard_view->priv->drag_timer_event);
       standard_view->priv->drag_timer_event = NULL;
     }
   else
-    thunar_gtk_menu_run (GTK_MENU (menu));
+    thunar_gtk_menu_run (GTK_MENU (context_menu));
 
   g_list_free_full (selected_items, (GDestroyNotify) gtk_tree_path_free);
 
-- 
GitLab