From 59e1d8332530d70c0ed9fabd01b896b83efc72b5 Mon Sep 17 00:00:00 2001 From: Gordon Norman Squash Date: Sun, 29 Sep 2024 18:43:44 -0400 Subject: Draw tree view rows in alternating colours ('zebra stripes') NOTE: Please see the README and the file `data/zebra-stripes.css` for information on how to actually activate the zebra stripes. --- README.md | 17 ++-- data/zebra-stripes.css | 27 +++++-- main.c | 2 + meson.build | 3 +- patches/treeview-zebra-stripes.c | 170 +++++++++++++++++++++++++++++++++++++++ screenshots/zebra-stripes.png | Bin 0 -> 53829 bytes 6 files changed, 206 insertions(+), 13 deletions(-) create mode 100644 patches/treeview-zebra-stripes.c create mode 100644 screenshots/zebra-stripes.png diff --git a/README.md b/README.md index 6c12d58..c5fabb4 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,16 @@ amount of documentation about each option. **Requires disabling the GTK setting `gtk-auto-mnemonics`.** +* Displays tree views with alternating row colours ('zebra stripes') if the + tree view contains more than one column. + + + + **Requires copying the contents of the file `data/zebra-stripes.css` into + your `~/.config/gtk-3.0/gtk.css` file; please see the file + `data/zebra-stripes.css` for more information, as there is a comment at the + top of that file with more things you may need to know.** + * Forces GTK to display normal dialog buttons at the bottom of stock dialogs (e.g. About dialog, color chooser, file chooser), instead of displaying the buttons in a headerbar at the top of the dialog -- even on Wayland. @@ -141,11 +151,6 @@ here. * Convert menu-like popovers into traditional menus. -* Restore 'zebra stripes' (alternating row colours in treeviews). - ## License -GNU LGPL-2. This is the same license under which GTK 3 is released. (This -module used to be LGPL-3 until I was warned that GTK 3 is licensed under LGPL-2 -[*only*](https://gitlab.gnome.org/GNOME/gtk/-/blob/main/COPYING). I didn't -know any project other than the Linux kernel was licensed that way.) +GNU LGPL-2. This is the same license under which GTK 3 is released. diff --git a/data/zebra-stripes.css b/data/zebra-stripes.css index 5f13d79..e89c09d 100644 --- a/data/zebra-stripes.css +++ b/data/zebra-stripes.css @@ -1,16 +1,31 @@ -/* TODO: This is currently unused, but when alternating treeview row colours - * are restored, this will be the default CSS code to activate them if the - * current theme doesn't provide its own code for this. +/* This file enables alternating light and dark row colours ('zebra stripes') + * in tree view widgets. These are general good default colours for light + * themes. You can enable zebra stripes by copying this file to your + * ~/.config/gtk-3.0/gtk.css file, or you can implement similar code in your + * theme CSS. + * + * Note the extra CSS selectors which theme only tree views with more than + * one column; generally speaking, zebra stripes are only necessary when the + * list or tree contains more than one column. If you really want zebra + * stripes on all tree views, regardless of column count, remove the extra + * selectors for ':not(.first):not(.last)', '.first:not(.last)', and + * ':not(.first).last'. */ -treeview:not(:selected).odd.sorted, -treeview:not(:selected).even:not(.sorted) +treeview.cell:not(.first):not(.last):not(:selected).odd.sorted, +treeview.cell.first:not(.last):not(:selected).odd.sorted, +treeview.cell:not(.first).last:not(:selected).odd.sorted, +treeview.cell:not(.first):not(.last):not(:selected).even:not(.sorted), +treeview.cell.first:not(.last):not(:selected).even:not(.sorted), +treeview.cell:not(.first).last:not(:selected).even:not(.sorted) { background: alpha(black, 0.07); } -treeview:not(:selected).even.sorted +treeview.cell:not(.first):not(.last):not(:selected).even.sorted, +treeview.cell.first:not(.last):not(:selected).even.sorted, +treeview.cell:not(.first).last:not(:selected).even.sorted { background: alpha(black, 0.13); } diff --git a/main.c b/main.c index ddfc7ab..731b58e 100644 --- a/main.c +++ b/main.c @@ -14,9 +14,11 @@ void icon_sizes_init (); void no_emojis_init (); void persistent_mnemonics_init (); void smaller_widgets_init (); +void treeview_zebra_stripes_init (); static const InitFunc init_funcs[] = { + treeview_zebra_stripes_init, smaller_widgets_init, /* No Emojis must be loaded before Button/Menu Icons since the former * overrides GtkMenuItem, the superclass of the GtkImageMenuItem class diff --git a/meson.build b/meson.build index b1417d0..4592d18 100644 --- a/meson.build +++ b/meson.build @@ -56,7 +56,8 @@ shared_module ('gtk3-classic-module', 'patches/icon-sizes.c', 'patches/no-emojis.c', 'patches/persistent-mnemonics.c', - 'patches/smaller-widgets.c' + 'patches/smaller-widgets.c', + 'patches/treeview-zebra-stripes.c' ], include_directories: [ include_dir ], dependencies: [ gtk_dep, gtk_unix_print_dep ], diff --git a/patches/treeview-zebra-stripes.c b/patches/treeview-zebra-stripes.c new file mode 100644 index 0000000..8dafa16 --- /dev/null +++ b/patches/treeview-zebra-stripes.c @@ -0,0 +1,170 @@ +/* Treeview Zebra Stripes: + * (based on gtk3-classic patch 'treeview__alternating_row_colours.patch'): + * + * Adds appropriate style classes to each cell in a GtkTreeView, so that themes + * can draw the rows of a treeview in alternating colours, also known as + * 'zebra stripes'. + */ + + +#include +#include +#include +#include + +#include + + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "Gtk3-Classic::Treeview-Zebra-Stripes" + + +static gboolean find_row_index (GtkTreeView * treeview, + GtkTreeModel * model, + GtkTreeIter * start_iter, + GtkTreePath * target_path, + int * index); + + +INTERCEPTED_CLASS_METHOD (gtk_cell_area, render, + (GtkCellArea * area, + GtkCellAreaContext * context, + GtkWidget * widget, + cairo_t * cr, + const GdkRectangle * background_area, + const GdkRectangle * cell_area, + GtkCellRendererState flags, + gboolean paint_focus), + void) + + +void treeview_zebra_stripes_init () +{ + GtkCellAreaClass * gtk_cell_area_class = + g_type_class_ref (GTK_TYPE_CELL_AREA); + + INTERCEPT_CLASS_METHOD (gtk_cell_area, GTK_CELL_AREA_CLASS, + render) + + g_type_class_unref (gtk_cell_area_class); +} + + +static void +new_gtk_cell_area_render +(GtkCellArea * area, + GtkCellAreaContext * context, + GtkWidget * widget, + cairo_t * cr, + const GdkRectangle * background_area, + const GdkRectangle * cell_area, + GtkCellRendererState flags, + gboolean paint_focus) +{ + GtkTreeView * treeview; + GtkTreeModel * model; + GtkStyleContext * style; + gint bin_x, bin_y; + GtkTreePath * path; + int index; + GtkTreeViewColumn * column; + GList * columns; + + + if (GTK_IS_TREE_VIEW (widget)) + { + treeview = GTK_TREE_VIEW (widget); + model = gtk_tree_view_get_model (treeview); + style = gtk_widget_get_style_context (widget); + columns = gtk_tree_view_get_columns (treeview); + + bin_x = cell_area->x + cell_area->width - 1; + bin_y = cell_area->y + cell_area->height - 1; + + gtk_tree_view_get_path_at_pos (treeview, bin_x, bin_y, + &path, &column, NULL, NULL); + if (path != NULL) + { + index = 0; + find_row_index (treeview, model, NULL, path, &index); + gtk_tree_path_free (path); + gtk_style_context_add_class (style, + ((index % 2) ? "even" : "odd")); + } + + if (column == columns->data) + gtk_style_context_add_class (style, "first"); + if (column == g_list_last (columns)->data) + gtk_style_context_add_class (style, "last"); + if ((flags & GTK_CELL_RENDERER_SORTED)) + gtk_style_context_add_class (style, "sorted"); + + gtk_render_background (style, cr, + background_area->x, background_area->y, + background_area->width, + background_area->height); + } + + + CALL_ORIGINAL_CLASS_METHOD (gtk_cell_area, render, + (area, context, widget, cr, + background_area, cell_area, + flags, paint_focus)); +} + + +/* Helper functions */ + +static gboolean +find_row_index +(GtkTreeView * treeview, + GtkTreeModel * model, + GtkTreeIter * start_iter, + GtkTreePath * target_path, + int * index) +{ + GtkTreeIter iter; + GtkTreeIter child_iter; + GtkTreePath * path; + + + if (start_iter == NULL) + { + if (! gtk_tree_model_get_iter_first (model, &iter)) + return TRUE; + } + else + iter = *start_iter; + + while (1) + { + path = gtk_tree_model_get_path (model, &iter); + + if (gtk_tree_path_compare (path, target_path) == 0) + { + gtk_tree_path_free (path); + return TRUE; + } + + if (gtk_tree_view_row_expanded (treeview, path)) + { + (*index)++; + gtk_tree_model_iter_children (model, &child_iter, &iter); + if (find_row_index (treeview, model, &child_iter, + target_path, index)) + { + gtk_tree_path_free (path); + return TRUE; + } + } + + gtk_tree_path_free (path); + if (! gtk_tree_model_iter_next (model, &iter)) + break; + else + (*index)++; + } + + + return FALSE; +} diff --git a/screenshots/zebra-stripes.png b/screenshots/zebra-stripes.png new file mode 100644 index 0000000..c6a9afb Binary files /dev/null and b/screenshots/zebra-stripes.png differ -- cgit