[gnome-software] Show the updates panel with sections
- From: Richard Hughes <rhughes src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-software] Show the updates panel with sections
- Date: Fri, 13 Jan 2017 09:28:28 +0000 (UTC)
commit dd31a31ffece0f1ab9c6b4bcf28ee5cfe27dd244
Author: Richard Hughes <richard hughsie com>
Date: Thu Jan 12 15:46:10 2017 +0000
Show the updates panel with sections
This updates the panel to the latest mockups as seen in:
https://raw.githubusercontent.com/gnome-design-team/gnome-mockups-software/master/wireframes/updates.png
src/gs-app-row.ui | 3 +-
src/gs-shell-installed.c | 6 +-
src/gs-shell-updates.c | 36 +------
src/gs-shell-updates.ui | 11 --
src/gs-update-list.c | 257 ++++++++++++++++++++++++++++++++++++++--------
src/gs-update-list.h | 12 ++-
src/gtk-style-hc.css | 13 +--
src/gtk-style.css | 13 +--
8 files changed, 243 insertions(+), 108 deletions(-)
---
diff --git a/src/gs-app-row.ui b/src/gs-app-row.ui
index d25ccab..5807d28 100644
--- a/src/gs-app-row.ui
+++ b/src/gs-app-row.ui
@@ -247,7 +247,7 @@
<property name="orientation">vertical</property>
<property name="halign">center</property>
<property name="valign">center</property>
- <property name="margin-right">24</property>
+ <property name="margin-right">6</property>
<child>
<object class="GtkBox" id="button_box">
<property name="visible">True</property>
@@ -257,6 +257,7 @@
<child>
<object class="GsProgressButton" id="button">
<property name="width_request">100</property>
+ <property name="margin-right">6</property>
<property name="halign">end</property>
</object>
<packing>
diff --git a/src/gs-shell-installed.c b/src/gs-shell-installed.c
index 7a49f08..f3e11a4 100644
--- a/src/gs-shell-installed.c
+++ b/src/gs-shell-installed.c
@@ -454,7 +454,8 @@ gs_shell_installed_list_header_func (GtkListBoxRow *row,
"xalign", 0.0,
NULL);
context = gtk_widget_get_style_context (header);
- gtk_style_context_add_class (context, "header-label");
+ gtk_style_context_add_class (context, "app-listbox-header");
+ gtk_style_context_add_class (context, "app-listbox-header-title");
} else if (!gs_shell_installed_is_addon_id_kind (gs_app_row_get_app (GS_APP_ROW (before))) &&
gs_shell_installed_is_addon_id_kind (gs_app_row_get_app (GS_APP_ROW (row)))) {
/* TRANSLATORS: This is the header dividing the normal
@@ -464,7 +465,8 @@ gs_shell_installed_list_header_func (GtkListBoxRow *row,
"xalign", 0.0,
NULL);
context = gtk_widget_get_style_context (header);
- gtk_style_context_add_class (context, "header-label");
+ gtk_style_context_add_class (context, "app-listbox-header");
+ gtk_style_context_add_class (context, "app-listbox-header-title");
} else {
header = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
}
diff --git a/src/gs-shell-updates.c b/src/gs-shell-updates.c
index 46f844e..1fcb577 100644
--- a/src/gs-shell-updates.c
+++ b/src/gs-shell-updates.c
@@ -92,7 +92,6 @@ struct _GsShellUpdates
GtkWidget *updates_box;
GtkWidget *button_updates_mobile;
GtkWidget *button_updates_offline;
- GtkWidget *fake_header_bar;
GtkWidget *label_updates_failed;
GtkWidget *label_updates_last_checked;
GtkWidget *label_updates_spinner;
@@ -250,7 +249,7 @@ gs_shell_updates_get_state_string (GsPluginStatus status)
static void
gs_shell_updates_update_ui_state (GsShellUpdates *self)
{
- GtkWidget *old_parent;
+ GsUpdateList *update_list;
gboolean allow_mobile_refresh = TRUE;
g_autofree gchar *checked_str = NULL;
g_autofree gchar *spinner_str = NULL;
@@ -328,8 +327,11 @@ gs_shell_updates_update_ui_state (GsShellUpdates *self)
gs_plugin_loader_get_network_available (self->plugin_loader));
/* headerbar update button */
+ update_list = GS_UPDATE_LIST (self->list_box_updates);
+ gs_update_list_set_force_headers (update_list,
+ self->result_flags & GS_SHELL_UPDATES_FLAG_HAS_UPGRADES);
gtk_widget_set_visible (self->button_update_all,
- self->result_flags != GS_SHELL_UPDATES_FLAG_NONE);
+ !gs_update_list_has_headers (update_list));
/* stack */
switch (self->state) {
@@ -384,31 +386,6 @@ gs_shell_updates_update_ui_state (GsShellUpdates *self)
break;
}
- /* any upgrades? */
- if (self->result_flags & GS_SHELL_UPDATES_FLAG_HAS_UPGRADES) {
- /* move header bar buttons to the fake header bar */
- g_object_ref (self->button_update_all);
- old_parent = gtk_widget_get_parent (self->button_update_all);
- if (old_parent != NULL)
- gtk_container_remove (GTK_CONTAINER (old_parent), self->button_update_all);
- gtk_header_bar_pack_end (GTK_HEADER_BAR (self->fake_header_bar), self->button_update_all);
- g_object_unref (self->button_update_all);
-
- gtk_widget_show (self->fake_header_bar);
- gtk_widget_show (self->upgrade_banner);
- } else {
- /* move header bar buttons to the real header bar */
- g_object_ref (self->button_update_all);
- old_parent = gtk_widget_get_parent (self->button_update_all);
- if (old_parent != NULL)
- gtk_container_remove (GTK_CONTAINER (old_parent), self->button_update_all);
- gtk_container_add (GTK_CONTAINER (self->header_end_box), self->button_update_all);
- g_object_unref (self->button_update_all);
-
- gtk_widget_hide (self->fake_header_bar);
- gtk_widget_hide (self->upgrade_banner);
- }
-
/* any updates? */
gtk_widget_set_visible (self->updates_box,
self->result_flags & GS_SHELL_UPDATES_FLAG_HAS_UPDATES);
@@ -591,7 +568,7 @@ gs_shell_updates_load (GsShellUpdates *self)
if (self->action_cnt > 0)
return;
- gs_container_remove_all (GTK_CONTAINER (self->list_box_updates));
+ gs_update_list_remove_all (GS_UPDATE_LIST (self->list_box_updates));
refine_flags = GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON |
GS_PLUGIN_REFINE_FLAGS_REQUIRE_UPDATE_DETAILS |
GS_PLUGIN_REFINE_FLAGS_REQUIRE_PROVENANCE |
@@ -1428,7 +1405,6 @@ gs_shell_updates_class_init (GsShellUpdatesClass *klass)
gtk_widget_class_bind_template_child (widget_class, GsShellUpdates, updates_box);
gtk_widget_class_bind_template_child (widget_class, GsShellUpdates, button_updates_mobile);
gtk_widget_class_bind_template_child (widget_class, GsShellUpdates, button_updates_offline);
- gtk_widget_class_bind_template_child (widget_class, GsShellUpdates, fake_header_bar);
gtk_widget_class_bind_template_child (widget_class, GsShellUpdates, label_updates_failed);
gtk_widget_class_bind_template_child (widget_class, GsShellUpdates, label_updates_last_checked);
gtk_widget_class_bind_template_child (widget_class, GsShellUpdates, label_updates_spinner);
diff --git a/src/gs-shell-updates.ui b/src/gs-shell-updates.ui
index d2eeccd..2ca87b9 100644
--- a/src/gs-shell-updates.ui
+++ b/src/gs-shell-updates.ui
@@ -79,17 +79,6 @@
<property name="visible">True</property>
<property name="orientation">vertical</property>
<child>
- <object class="GtkHeaderBar" id="fake_header_bar">
- <property name="visible">True</property>
- <property name="hexpand">True</property>
- <property name="vexpand">False</property>
- <property name="title" translatable="yes">Other Updates</property>
- <style>
- <class name="fake-header-bar"/>
- </style>
- </object>
- </child>
- <child>
<object class="GsUpdateList" id="list_box_updates">
<property name="visible">True</property>
<property name="can_focus">True</property>
diff --git a/src/gs-update-list.c b/src/gs-update-list.c
index eb37717..6e133c1 100644
--- a/src/gs-update-list.c
+++ b/src/gs-update-list.c
@@ -29,12 +29,24 @@
#include "gs-app.h"
#include "gs-app-row.h"
+#include "gs-common.h"
+
+typedef enum {
+ GS_UPDATE_LIST_SECTION_OFFLINE_FIRMWARE,
+ GS_UPDATE_LIST_SECTION_OFFLINE,
+ GS_UPDATE_LIST_SECTION_ONLINE,
+ GS_UPDATE_LIST_SECTION_ONLINE_FIRMWARE,
+ GS_UPDATE_LIST_SECTION_LAST
+} GsUpdateListSection;
typedef struct
{
GtkSizeGroup *sizegroup_image;
GtkSizeGroup *sizegroup_name;
GtkSizeGroup *sizegroup_button;
+ GtkSizeGroup *sizegroup_header;
+ gboolean force_headers;
+ GsUpdateListSection sections_cnt[GS_UPDATE_LIST_SECTION_LAST];
} GsUpdateListPrivate;
enum {
@@ -54,13 +66,39 @@ gs_update_list_button_clicked_cb (GsAppRow *app_row,
g_signal_emit (update_list, signals[SIGNAL_BUTTON_CLICKED], 0, app);
}
+static GsUpdateListSection
+gs_update_list_get_app_section (GsApp *app)
+{
+ if (gs_app_get_state (app) == AS_APP_STATE_UPDATABLE_LIVE) {
+ if (gs_app_get_kind (app) == AS_APP_KIND_FIRMWARE)
+ return GS_UPDATE_LIST_SECTION_ONLINE_FIRMWARE;
+ return GS_UPDATE_LIST_SECTION_ONLINE;
+ }
+ if (gs_app_get_kind (app) == AS_APP_KIND_FIRMWARE)
+ return GS_UPDATE_LIST_SECTION_OFFLINE_FIRMWARE;
+ return GS_UPDATE_LIST_SECTION_OFFLINE;
+}
+
+void
+gs_update_list_remove_all (GsUpdateList *update_list)
+{
+ GsUpdateListPrivate *priv = gs_update_list_get_instance_private (update_list);
+ for (guint i = 0; i < GS_UPDATE_LIST_SECTION_LAST; i++)
+ priv->sections_cnt[i] = 0;
+ gs_container_remove_all (GTK_CONTAINER (update_list));
+}
+
void
-gs_update_list_add_app (GsUpdateList *update_list,
- GsApp *app)
+gs_update_list_add_app (GsUpdateList *update_list, GsApp *app)
{
GsUpdateListPrivate *priv = gs_update_list_get_instance_private (update_list);
+ GsUpdateListSection section;
GtkWidget *app_row;
+ /* keep track */
+ section = gs_update_list_get_app_section (app);
+ priv->sections_cnt[section]++;
+
app_row = gs_app_row_new (app);
gs_app_row_set_show_update (GS_APP_ROW (app_row), TRUE);
gs_app_row_set_show_buttons (GS_APP_ROW (app_row), TRUE);
@@ -75,6 +113,34 @@ gs_update_list_add_app (GsUpdateList *update_list,
gtk_widget_show (app_row);
}
+/* returns if the updates have section headers */
+gboolean
+gs_update_list_has_headers (GsUpdateList *update_list)
+{
+ GsUpdateListPrivate *priv = gs_update_list_get_instance_private (update_list);
+ guint cnt = 0;
+
+ /* forced on by the distro upgrade for example */
+ if (priv->force_headers)
+ return TRUE;
+
+ /* more than one type of thing */
+ for (guint i = 0; i < GS_UPDATE_LIST_SECTION_LAST; i++) {
+ if (priv->sections_cnt[i] > 0)
+ cnt++;
+ }
+ return cnt > 1;
+}
+
+/* forces the update list into having section headers even if it does not need
+ * them, for instance if we're showing a big system upgrade banner at the top */
+void
+gs_update_list_set_force_headers (GsUpdateList *update_list, gboolean force_headers)
+{
+ GsUpdateListPrivate *priv = gs_update_list_get_instance_private (update_list);
+ priv->force_headers = force_headers;
+}
+
GsAppList *
gs_update_list_get_apps (GsUpdateList *update_list)
{
@@ -91,20 +157,116 @@ gs_update_list_get_apps (GsUpdateList *update_list)
return apps;
}
-static gboolean
-is_addon_id_kind (GsApp *app)
+static void
+gs_update_list_emit_clicked_for_section (GsUpdateList *update_list,
+ GsUpdateListSection section)
{
- AsAppKind id_kind;
- id_kind = gs_app_get_kind (app);
- if (id_kind == AS_APP_KIND_DESKTOP)
- return FALSE;
- if (id_kind == AS_APP_KIND_WEB_APP)
- return FALSE;
- if (id_kind == AS_APP_KIND_FIRMWARE)
- return FALSE;
- if (id_kind == AS_APP_KIND_RUNTIME)
- return FALSE;
- return TRUE;
+ g_autoptr(GList) children = NULL;
+ children = gtk_container_get_children (GTK_CONTAINER (update_list));
+ for (GList *l = children; l != NULL; l = l->next) {
+ GsAppRow *app_row = GS_APP_ROW (l->data);
+ GsApp *app = gs_app_row_get_app (app_row);
+ if (gs_update_list_get_app_section (app) != section)
+ continue;
+ g_signal_emit (update_list, signals[SIGNAL_BUTTON_CLICKED], 0, app);
+ }
+}
+
+static void
+gs_update_list_update_offline_firmware_cb (GtkButton *button,
+ GsUpdateList *update_list)
+{
+ gs_update_list_emit_clicked_for_section (update_list,
+ GS_UPDATE_LIST_SECTION_OFFLINE_FIRMWARE);
+}
+
+static void
+gs_update_list_update_offline_cb (GtkButton *button, GsUpdateList *update_list)
+{
+ gs_update_list_emit_clicked_for_section (update_list,
+ GS_UPDATE_LIST_SECTION_OFFLINE);
+}
+
+static void
+gs_update_list_update_online_cb (GtkButton *button, GsUpdateList *update_list)
+{
+ gs_update_list_emit_clicked_for_section (update_list,
+ GS_UPDATE_LIST_SECTION_ONLINE);
+}
+
+static GtkWidget *
+gs_update_list_get_section_header (GsUpdateList *update_list,
+ GsUpdateListSection section)
+{
+ GsUpdateListPrivate *priv = gs_update_list_get_instance_private (update_list);
+ GtkStyleContext *context;
+ GtkWidget *header;
+ GtkWidget *label;
+ GtkWidget *button = NULL;
+
+ /* get labels and buttons for everything */
+ if (section == GS_UPDATE_LIST_SECTION_OFFLINE_FIRMWARE) {
+ /* TRANSLATORS: This is the header for system firmware that
+ * requires a reboot to apply */
+ label = gtk_label_new (_("Integrated Firmware"));
+ /* TRANSLATORS: This is the button for upgrading all
+ * system firmware */
+ button = gtk_button_new_with_label (_("Restart & Update"));
+ g_signal_connect (button, "clicked",
+ G_CALLBACK (gs_update_list_update_offline_firmware_cb),
+ update_list);
+ } else if (section == GS_UPDATE_LIST_SECTION_OFFLINE) {
+ /* TRANSLATORS: This is the header for offline OS and offline
+ * app updates that require a reboot to apply */
+ label = gtk_label_new (_("Requires Restart"));
+ /* TRANSLATORS: This is the button for upgrading all
+ * offline updates */
+ button = gtk_button_new_with_label (_("Restart & Update"));
+ g_signal_connect (button, "clicked",
+ G_CALLBACK (gs_update_list_update_offline_cb),
+ update_list);
+ } else if (section == GS_UPDATE_LIST_SECTION_ONLINE) {
+ /* TRANSLATORS: This is the header for online runtime and
+ * app updates, typically flatpaks or snaps */
+ label = gtk_label_new (_("Application Updates"));
+ /* TRANSLATORS: This is the button for upgrading all
+ * online-updatable applications */
+ button = gtk_button_new_with_label (_("Update All"));
+ g_signal_connect (button, "clicked",
+ G_CALLBACK (gs_update_list_update_online_cb),
+ update_list);
+ } else if (section == GS_UPDATE_LIST_SECTION_ONLINE_FIRMWARE) {
+ /* TRANSLATORS: This is the header for device firmware that can
+ * be installed online */
+ label = gtk_label_new (_("Device Firmware"));
+ } else {
+ g_assert_not_reached ();
+ }
+
+ /* create header */
+ header = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 3);
+ gtk_size_group_add_widget (priv->sizegroup_header, header);
+ context = gtk_widget_get_style_context (header);
+ gtk_style_context_add_class (context, "app-listbox-header");
+
+ /* put label into the header */
+ gtk_box_pack_start (GTK_BOX (header), label, TRUE, TRUE, 0);
+ gtk_widget_set_visible (label, TRUE);
+ gtk_widget_set_margin_start (label, 6);
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ context = gtk_widget_get_style_context (label);
+ gtk_style_context_add_class (context, "app-listbox-header-title");
+
+ /* add button if one is specified */
+ if (button != NULL) {
+ gtk_box_pack_end (GTK_BOX (header), button, FALSE, FALSE, 0);
+ gtk_widget_set_visible (button, TRUE);
+ gtk_widget_set_margin_end (button, 6);
+ gtk_size_group_add_widget (priv->sizegroup_button, button);
+ }
+
+ /* success */
+ return header;
}
static void
@@ -112,25 +274,24 @@ list_header_func (GtkListBoxRow *row,
GtkListBoxRow *before,
gpointer user_data)
{
- GtkStyleContext *context;
+ GsApp *app = gs_app_row_get_app (GS_APP_ROW (row));
+ GsUpdateListSection before_section = GS_UPDATE_LIST_SECTION_LAST;
+ GsUpdateListSection section;
+ GsUpdateList *update_list = GS_UPDATE_LIST (user_data);
GtkWidget *header;
/* first entry */
gtk_list_box_row_set_header (row, NULL);
- if (before == NULL)
- return;
-
- /* desktop -> addons */
- if (!is_addon_id_kind (gs_app_row_get_app (GS_APP_ROW (before))) &&
- is_addon_id_kind (gs_app_row_get_app (GS_APP_ROW (row)))) {
- /* TRANSLATORS: This is the header dividing the normal
- * applications and the addons */
- header = gtk_label_new (_("Add-ons"));
- g_object_set (header,
- "xalign", 0.0,
- NULL);
- context = gtk_widget_get_style_context (header);
- gtk_style_context_add_class (context, "header-label");
+ if (before != NULL) {
+ GsApp *before_app = gs_app_row_get_app (GS_APP_ROW (before));
+ before_section = gs_update_list_get_app_section (before_app);
+ }
+
+ /* section changed or forced to have headers */
+ section = gs_update_list_get_app_section (app);
+ if (gs_update_list_has_headers (update_list) &&
+ before_section != section) {
+ header = gs_update_list_get_section_header (update_list, section);
} else {
header = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
}
@@ -144,33 +305,33 @@ get_app_sort_key (GsApp *app)
key = g_string_sized_new (64);
- /* sort by kind */
- switch (gs_app_get_kind (app)) {
- case AS_APP_KIND_OS_UPDATE:
- g_string_append (key, "1:");
- break;
- default:
- g_string_append (key, "2:");
- break;
- }
+ /* Sections:
+ * 1. offline integrated firmware
+ * 2. offline os updates (OS-update, apps, runtimes, addons, other)
+ * 3. online apps (apps, runtimes, addons, other)
+ * 4. online device firmware */
+ g_string_append_printf (key, "%u:", gs_update_list_get_app_section (app));
/* sort desktop files, then addons */
switch (gs_app_get_kind (app)) {
- case AS_APP_KIND_FIRMWARE:
+ case AS_APP_KIND_OS_UPDATE:
g_string_append (key, "1:");
break;
case AS_APP_KIND_DESKTOP:
g_string_append (key, "2:");
break;
- default:
+ case AS_APP_KIND_RUNTIME:
g_string_append (key, "3:");
break;
+ case AS_APP_KIND_ADDON:
+ case AS_APP_KIND_WEB_APP:
+ g_string_append (key, "4:");
+ break;
+ default:
+ g_string_append (key, "5:");
+ break;
}
- /* sort by install date */
- g_string_append_printf (key, "%09" G_GUINT64_FORMAT ":",
- G_MAXUINT64 - gs_app_get_install_date (app));
-
/* finally, sort by short name */
g_string_append (key, gs_app_get_name (app));
return g_string_free (key, FALSE);
@@ -199,6 +360,7 @@ gs_update_list_dispose (GObject *object)
g_clear_object (&priv->sizegroup_image);
g_clear_object (&priv->sizegroup_name);
g_clear_object (&priv->sizegroup_button);
+ g_clear_object (&priv->sizegroup_header);
G_OBJECT_CLASS (gs_update_list_parent_class)->dispose (object);
}
@@ -210,6 +372,7 @@ gs_update_list_init (GsUpdateList *update_list)
priv->sizegroup_image = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
priv->sizegroup_name = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
priv->sizegroup_button = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+ priv->sizegroup_header = gtk_size_group_new (GTK_SIZE_GROUP_VERTICAL);
gtk_list_box_set_header_func (GTK_LIST_BOX (update_list),
list_header_func,
@@ -217,6 +380,10 @@ gs_update_list_init (GsUpdateList *update_list)
gtk_list_box_set_sort_func (GTK_LIST_BOX (update_list),
list_sort_func,
update_list, NULL);
+
+ /* set each section count to zero */
+ for (guint i = 0; i < GS_UPDATE_LIST_SECTION_LAST; i++)
+ priv->sections_cnt[i] = 0;
}
static void
diff --git a/src/gs-update-list.h b/src/gs-update-list.h
index 96e3351..6e836b5 100644
--- a/src/gs-update-list.h
+++ b/src/gs-update-list.h
@@ -40,10 +40,14 @@ struct _GsUpdateListClass
GsApp *app);
};
-GtkWidget *gs_update_list_new (void);
-void gs_update_list_add_app (GsUpdateList *update_list,
- GsApp *app);
-GsAppList *gs_update_list_get_apps (GsUpdateList *update_list);
+GtkWidget *gs_update_list_new (void);
+void gs_update_list_add_app (GsUpdateList *update_list,
+ GsApp *app);
+void gs_update_list_remove_all (GsUpdateList *update_list);
+GsAppList *gs_update_list_get_apps (GsUpdateList *update_list);
+gboolean gs_update_list_has_headers (GsUpdateList *update_list);
+void gs_update_list_set_force_headers (GsUpdateList *update_list,
+ gboolean force_headers);
G_END_DECLS
diff --git a/src/gtk-style-hc.css b/src/gtk-style-hc.css
index 1a78e64..3e1af75 100644
--- a/src/gtk-style-hc.css
+++ b/src/gtk-style-hc.css
@@ -136,20 +136,17 @@
font-size: smaller;
}
-.fake-header-bar {
- background-color: white;
- background-image: none;
- border-radius: 0;
-}
-
-.header-label {
- font-size: larger;
+.app-listbox-header {
padding: 6px;
background-image: none;
background-color: #babdb6;
border-color: #000000;
}
+.app-listbox-header-title {
+ font-size: larger;
+}
+
.image-list {
background-color: transparent;
}
diff --git a/src/gtk-style.css b/src/gtk-style.css
index 4dae833..ee92d78 100644
--- a/src/gtk-style.css
+++ b/src/gtk-style.css
@@ -297,18 +297,17 @@
font-size: smaller;
}
-.fake-header-bar {
- all: unset;
-}
-
-.header-label {
- font-size: 100%;
- font-weight: bold;
+.app-listbox-header {
padding: 6px 6px 6px 10px;
background-image: none;
background-color: shade(@theme_bg_color, 0.9);
}
+.app-listbox-header-title {
+ font-size: 100%;
+ font-weight: bold;
+}
+
.image-list {
background-color: transparent;
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]