[gnumeric] Conditional styling: rework the higher levels.



commit fff62cb9489ca6d01dabf6a2537ad994b90043a7
Author: Morten Welinder <terra gnome org>
Date:   Thu Aug 13 21:49:30 2020 -0400

    Conditional styling: rework the higher levels.

 NEWS                   |   3 +
 src/Makefile.am        |   2 +
 src/dependent.c        | 159 +----------
 src/dependent.h        |  11 +-
 src/gnumeric-fwd.h     |   1 +
 src/mstyle.c           | 113 +++-----
 src/ranges.c           |  19 +-
 src/sheet-conditions.c | 697 +++++++++++++++++++++++++++++++++++++++++++++++++
 src/sheet-conditions.h |  23 ++
 src/sheet.c            |   4 +
 src/sheet.h            |   2 +
 src/style-conditions.c | 171 +++++++++++-
 src/style-conditions.h |  19 +-
 src/wbc-gtk.c          |   8 +
 14 files changed, 976 insertions(+), 256 deletions(-)
---
diff --git a/NEWS b/NEWS
index f02395f94..34a37720d 100644
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,8 @@
 Gnumeric 1.12.49
 
+Morten:
+       * Rework conditional styling's upper level.
+
 --------------------------------------------------------------------------
 Gnumeric 1.12.48
 
diff --git a/src/Makefile.am b/src/Makefile.am
index e9267ff00..32934ded7 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -447,6 +447,7 @@ libspreadsheet_la_SOURCES =         \
        sf-gamma.c                              \
        sf-trig.c                               \
        sheet-autofill.c                        \
+       sheet-conditions.c                      \
        sheet-control-gui.c                     \
        sheet-control.c                         \
        sheet-diff.c                            \
@@ -586,6 +587,7 @@ libspreadsheet_include_HEADERS =            \
        sf-gamma.h                              \
        sf-trig.h                               \
        sheet-autofill.h                        \
+       sheet-conditions.h                      \
        sheet-control-gui-priv.h                \
        sheet-control-gui.h                     \
        sheet-control-priv.h                    \
diff --git a/src/dependent.c b/src/dependent.c
index fa9f768f1..95e68ab99 100644
--- a/src/dependent.c
+++ b/src/dependent.c
@@ -339,32 +339,6 @@ static const GnmDependentClass managed_dep_class = {
        managed_dep_debug_name,
 };
 
-static GnmCellPos *managed_dep_pos (GnmDependent const *dep);
-static const GnmDependentClass managed_pos_dep_class = {
-       dummy_dep_eval,
-       NULL,
-       NULL,
-       managed_dep_pos,
-       managed_dep_debug_name,
-};
-
-static void style_dep_eval (GnmDependent *dep);
-static GSList *style_dep_changed (GnmDependent *dep);
-static GnmCellPos *style_dep_pos (GnmDependent const *dep);
-static void style_dep_debug_name (GnmDependent const *dep, GString *target);
-static const GnmDependentClass style_dep_class = {
-       style_dep_eval,
-       NULL,
-       style_dep_changed,
-       style_dep_pos,
-       style_dep_debug_name,
-};
-typedef struct {
-       GnmDependent base;
-       GnmCellPos pos;
-} GnmStyleDependent;
-
-
 static GPtrArray *dep_classes = NULL;
 
 void
@@ -379,8 +353,6 @@ dependent_types_init (void)
        g_ptr_array_add (dep_classes, (gpointer)&dynamic_dep_class);
        g_ptr_array_add (dep_classes, (gpointer)&name_dep_class);
        g_ptr_array_add (dep_classes, (gpointer)&managed_dep_class);
-       g_ptr_array_add (dep_classes, (gpointer)&managed_pos_dep_class);
-       g_ptr_array_add (dep_classes, (gpointer)&style_dep_class);
 
 #if USE_POOLS
        micro_few_pool =
@@ -1371,44 +1343,6 @@ workbook_unlink_3d_dep (GnmDependent *dep)
        g_hash_table_remove (wb->sheet_order_dependents, dep);
 }
 
-/**
- * gnm_dep_style_dependency:
- * @sheet:
- * @texpr:
- * @r:
- * @accum: (inout) (transfer full) (element-type GnmDependent):
- **/
-void
-gnm_dep_style_dependency (Sheet *sheet,
-                         GnmExprTop const *texpr,
-                         GnmRange const *r,
-                         GPtrArray *accum)
-{
-       int row, col;
-
-       /*
-        * FIXME: Maybe do better for an expression that is just an
-        * absolute ref.
-        */
-
-       for (row = r->start.row; row <= r->end.row; row++) {
-               for (col = r->start.col; col <= r->end.col; col++) {
-                       GnmStyleDependent *sd = g_new0 (GnmStyleDependent, 1);
-                       GnmDependent *dep = &sd->base;
-
-                       dep->sheet = sheet;
-                       dep->flags = DEPENDENT_STYLE;
-                       dep->texpr = NULL;
-                       sd->pos.col = col;
-                       sd->pos.row = row;
-
-                       dependent_set_expr (dep, texpr);
-                       dependent_link (dep);
-                       g_ptr_array_add (accum, dep);
-               }
-       }
-}
-
 /*****************************************************************************/
 
 static void
@@ -1486,14 +1420,6 @@ dependent_managed_init (GnmDepManaged *dep, Sheet *sheet)
        dep->base.sheet = sheet;
 }
 
-void
-dependent_managed_pos_init (GnmDepManaged *dep, Sheet *sheet, GnmCellPos const *pos)
-{
-       dependent_managed_init (dep, sheet);
-       dep->base.flags = DEPENDENT_MANAGED_POS;
-       dep->pos = *pos;
-}
-
 GnmExprTop const *
 dependent_managed_get_expr (GnmDepManaged const *dep)
 {
@@ -1544,82 +1470,6 @@ managed_dep_debug_name (GnmDependent const *dep, GString *target)
        g_string_append_printf (target, "Managed%p", (void *)dep);
 }
 
-static GnmCellPos *
-managed_dep_pos (GnmDependent const *dep)
-{
-       return &((GnmDepManaged*)dep)->pos;
-}
-
-/*****************************************************************************/
-
-static gboolean
-debug_style_deps (void)
-{
-       static int debug = -1;
-       if (debug < 0)
-               debug = gnm_debug_flag ("style-deps");
-       return debug;
-}
-
-static void
-style_dep_unrender (GnmDependent *dep, const char *what)
-{
-       GnmCellPos const *pos = dependent_pos (dep);
-       GnmCell *cell;
-       Sheet *sheet = dep->sheet;
-       GnmRange r;
-
-       if (debug_style_deps ())
-               g_printerr ("StyleDep %p at %s %s\n",
-                           dep, cellpos_as_string (pos), what);
-
-       /*
-        * If the cell exists, unrender it so format changes can take
-        * effect.
-        */
-       cell = sheet_cell_get (sheet, pos->col, pos->row);
-       if (cell)
-               gnm_cell_unrender (cell);
-
-       // Redraws may involve computation (via conditional styling,
-       // for example) so doing it now is no good.  See #480 for a
-       // particular nasty example involving conditional styling and
-       // dynamic dependents.
-       range_init_cellpos (&r, pos);
-       sheet_queue_redraw_range (sheet, &r);
-}
-
-static void
-style_dep_eval (GnmDependent *dep)
-{
-       /*
-        * It is possible that the cell has been rendered between ::changed
-        * was called and now.
-        */
-       style_dep_unrender (dep, "being evaluated");
-}
-
-static GSList *
-style_dep_changed (GnmDependent *dep)
-{
-       style_dep_unrender (dep, "changed");
-       return NULL;
-}
-
-static GnmCellPos *
-style_dep_pos (GnmDependent const *dep)
-{
-       return &((GnmStyleDependent*)dep)->pos;
-}
-
-static void
-style_dep_debug_name (GnmDependent const *dep, GString *target)
-{
-       g_string_append_printf (target, "StyleDep@%s[%p]",
-                               cellpos_as_string (style_dep_pos (dep)),
-                               dep);
-}
-
 /*****************************************************************************/
 
 static void
@@ -3311,7 +3161,10 @@ gnm_dep_container_dump (GnmDepContainer const *deps,
        gnm_dep_container_sanity_check (deps);
 
        alldeps = g_hash_table_new (g_direct_hash, g_direct_equal);
-       SHEET_FOREACH_DEPENDENT (sheet, dep, g_hash_table_insert (alldeps, dep, dep););
+       SHEET_FOREACH_DEPENDENT (sheet, dep,
+                                if (!dependent_is_cell (dep))
+                                        g_hash_table_insert (alldeps, dep, dep);
+       );
 
        for (i = 0; i < deps->buckets; i++) {
                GHashTable *hash = deps->range_hash[i];
@@ -3350,7 +3203,7 @@ gnm_dep_container_dump (GnmDepContainer const *deps,
                GHashTableIter hiter;
                gpointer key, value;
 
-               g_printerr ("  Dynamic hash size %d: cells that depend on dynamic dependencies\n",
+               g_printerr ("  Dynamic hash size %d: dependents that depend on dynamic dependencies\n",
                            g_hash_table_size (deps->dynamic_deps));
                g_hash_table_iter_init (&hiter, deps->dynamic_deps);
                while (g_hash_table_iter_next (&hiter, &key, &value)) {
@@ -3377,7 +3230,7 @@ gnm_dep_container_dump (GnmDepContainer const *deps,
                GHashTableIter hiter;
                gpointer key;
 
-               g_printerr ("  Dependencies of sheet not listed above:\n");
+               g_printerr ("  Dependencies of sheet not listed above (excluding cells):\n");
                g_hash_table_iter_init (&hiter, alldeps);
                while (g_hash_table_iter_next (&hiter, &key, NULL)) {
                        GnmDependent *dep = key;
diff --git a/src/dependent.h b/src/dependent.h
index dd56ec003..943dcfecf 100644
--- a/src/dependent.h
+++ b/src/dependent.h
@@ -31,8 +31,6 @@ typedef enum {
        DEPENDENT_DYNAMIC_DEP      = 0x00000002,        /* builtin type */
        DEPENDENT_NAME             = 0x00000003,        /* builtin pseudo type */
        DEPENDENT_MANAGED          = 0x00000004,        /* builtin type */
-       DEPENDENT_MANAGED_POS      = 0x00000005,        /* builtin type */
-       DEPENDENT_STYLE            = 0x00000006,        /* builtin type */
        DEPENDENT_TYPE_MASK        = 0x00000fff,
 
        /* Linked into the workbook wide expression list */
@@ -116,11 +114,6 @@ void dependents_revive_sheet      (Sheet *sheet);
 void workbook_queue_all_recalc   (Workbook *wb);
 void workbook_queue_volatile_recalc (Workbook *wb);
 
-void gnm_dep_style_dependency (Sheet *sheet,
-                              GnmExprTop const *texpr,
-                              GnmRange const *r,
-                              GPtrArray *accum);
-
 GnmDepContainer *gnm_dep_container_new  (Sheet *sheet);
 void            gnm_dep_container_dump (GnmDepContainer const *deps,
                                         Sheet *sheet);
@@ -132,16 +125,14 @@ void             gnm_dep_container_resize (GnmDepContainer *deps, int rows);
 
 typedef struct {
        GnmDependent base;
-       GnmCellPos pos;
 } GnmDepManaged;
 
 void dependent_managed_init (GnmDepManaged *dep, Sheet *sheet);
-void dependent_managed_pos_init (GnmDepManaged *dep, Sheet *sheet, GnmCellPos const *pos);
 void dependent_managed_set_expr (GnmDepManaged *dep, GnmExprTop const *texpr);
 GnmExprTop const *dependent_managed_get_expr (GnmDepManaged const *dep);
 void dependent_managed_set_sheet (GnmDepManaged *dep, Sheet *sheet);
 
-
+// ----------------------------------------------------------------------------
 
 #define DEPENDENT_CONTAINER_FOREACH_DEPENDENT(dc, dep, code)   \
   do {                                                         \
diff --git a/src/gnumeric-fwd.h b/src/gnumeric-fwd.h
index 92a99e88d..c2a04246d 100644
--- a/src/gnumeric-fwd.h
+++ b/src/gnumeric-fwd.h
@@ -71,6 +71,7 @@ typedef struct _GnmSearchReplace      GnmSearchReplace;
 typedef struct _GnmSheetSize           GnmSheetSize;
 typedef struct _GnmSheetSlicer         GnmSheetSlicer;
 typedef struct _GnmSheetStyleData       GnmSheetStyleData;
+typedef struct GnmSheetConditionsData_  GnmSheetConditionsData;
 typedef struct _GnmSortData            GnmSortData;
 typedef struct _GnmStfExport GnmStfExport;
 typedef struct _GnmStyle               GnmStyle;
diff --git a/src/mstyle.c b/src/mstyle.c
index 43491c59c..f0cd89c5c 100644
--- a/src/mstyle.c
+++ b/src/mstyle.c
@@ -16,6 +16,7 @@
 #include <style-font.h>
 #include <style-color.h>
 #include <style-conditions.h>
+#include <sheet-conditions.h>
 #include <validation.h>
 #include <pattern.h>
 #include <hlink.h>
@@ -1041,6 +1042,14 @@ gnm_style_link_sheet (GnmStyle *style, Sheet *sheet)
        style = link_border_colors (style, auto_color, style_is_orig);
        style_color_unref (auto_color);
 
+       if (elem_is_set (style, MSTYLE_CONDITIONS) && style->conditions) {
+               // We actually change the style here, but the resulting
+               // ->conditions should be equivalent.
+               GnmStyleConditions *sc_new = sheet_conditions_share_conditions_add (style->conditions);
+               if (sc_new)
+                       gnm_style_set_conditions (style, g_object_ref (sc_new));
+       }
+
        style->linked_sheet = sheet;
        style->link_count = 1;
 
@@ -1066,6 +1075,8 @@ gnm_style_unlink (GnmStyle *style)
 
        d(("unlink %p = %d\n", style, style->link_count-1));
        if (style->link_count-- == 1) {
+               if (elem_is_set (style, MSTYLE_CONDITIONS) && style->conditions)
+                       sheet_conditions_share_conditions_remove (style->conditions);
                sheet_style_unlink (style->linked_sheet, style);
                style->linked_sheet = NULL;
                gnm_style_unref (style);
@@ -2304,27 +2315,6 @@ gnm_style_get_cond_style (GnmStyle const *style, int ix)
        return g_ptr_array_index (style->cond_styles, ix);
 }
 
-
-/*
- * Just a simple version for now.  We can also ignore most function
- * calls[1] and self-references[2].
- *
- * [1] Excluding volatile (TODAY, ...) and those that can create references
- * outside the arguments (INDIRECT).
- *
- * [2] References that print like A1 when used in A1.
- */
-static gboolean
-cond_expr_harmless (GnmExpr const *expr)
-{
-       GnmValue const *v = gnm_expr_get_constant (expr);
-       if (v && !VALUE_IS_CELLRANGE (v))
-               return TRUE;
-
-       return FALSE;
-}
-
-
 void
 gnm_style_link_dependents (GnmStyle *style, GnmRange const *r)
 {
@@ -2336,79 +2326,46 @@ gnm_style_link_dependents (GnmStyle *style, GnmRange const *r)
 
        sheet = style->linked_sheet;
 
-       /*
-        * Conditional formatting.
-        *
-        * We need to trigger a reformatting of the cell if a cell referenced
-        * by the condition changes.
-        */
+       // ----------------------------------------
+
+       // Conditional formatting.
+       //
+       // We need to trigger a reformatting of the cell if a cell referenced
+       // by the condition changes.
        sc = elem_is_set (style, MSTYLE_CONDITIONS)
                ? gnm_style_get_conditions (style)
                : NULL;
        if (sc) {
-               GnmParsePos pp;
-               GPtrArray const *conds = gnm_style_conditions_details (sc);
-               guint ui;
-
-               parse_pos_init (&pp, NULL, sheet, r->start.col, r->start.row);
-
-               for (ui = 0; conds && ui < conds->len; ui++) {
-                       GnmStyleCond const *c = g_ptr_array_index (conds, ui);
-                       guint ei;
-
-                       for (ei = 0; ei < 2; ei++) {
-                               GnmExprTop const *texpr =
-                                       gnm_style_cond_get_expr (c, ei);
-                               char *s = NULL;
-                               if (!texpr)
-                                       continue;
-
-                               if (debug_style_deps)
-                                       s = gnm_expr_top_as_string (texpr, &pp,
-                                                                   sheet_get_conventions (sheet));
-
-                               if (cond_expr_harmless (texpr->expr)) {
-                                       if (debug_style_deps) {
-                                               g_printerr ("Not linking %s %d:%d for %p: %s (harmless)\n",
-                                                           range_as_string (r), ui, ei, style, s);
-                                               g_free (s);
-                                       }
-                                       continue;
-                               }
-
-                               if (debug_style_deps) {
-                                       g_printerr ("Linking %s %d:%d for %p: %s\n",
-                                                   range_as_string (r), ui, ei, style, s);
-                                       g_free (s);
-                               }
-
-                               if (!style->deps)
-                                       style->deps = g_ptr_array_new ();
-                               gnm_dep_style_dependency
-                                       (sheet, texpr, r, style->deps);
-                       }
-               }
+               sheet_conditions_add (sheet, r, style);
        }
 
-       /*
-        * Validations.
-        *
-        * We can probably ignore those.  If a dependent cell changes such
-        * that a validation condition is no longer satisfied, it is
-        * grandfathered in as valid.
-        */
-
-       /* The style owns the deps.  */
+       // ----------------------------------------
+       // Validations.
+       //
+       // We can probably ignore those.  If a dependent cell changes such
+       // that a validation condition is no longer satisfied, it is
+       // grandfathered in as valid.
 }
 
 void
 gnm_style_unlink_dependents (GnmStyle *style, GnmRange const *r)
 {
        unsigned ui, k;
+       GnmStyleConditions *sc;
+       Sheet *sheet;
 
        g_return_if_fail (style != NULL);
        g_return_if_fail (r != NULL);
 
+       sheet = style->linked_sheet;
+
+       sc = elem_is_set (style, MSTYLE_CONDITIONS)
+               ? gnm_style_get_conditions (style)
+               : NULL;
+       if (sc) {
+               sheet_conditions_remove (sheet, r, style);
+       }
+
        if (!style->deps)
                return;
 
diff --git a/src/ranges.c b/src/ranges.c
index a4e786ccd..21a550c37 100644
--- a/src/ranges.c
+++ b/src/ranges.c
@@ -902,7 +902,8 @@ gnm_range_equal (const GnmRange *a, const GnmRange *b)
  * Returns: a value that is negative if range @a comes before range @b;
  * zero if the two ranges are equal; positive if range @a comes after
  * range @b.  The order imposed is lexicographical by starting row,
- * then column, then ending row, then column.
+ * then column, then ending row, then column.  In other words, the order
+ * is A1, B1, A2, B2.
  */
 int
 gnm_range_compare (GnmRange const *a, GnmRange const *b)
@@ -915,6 +916,18 @@ gnm_range_compare (GnmRange const *a, GnmRange const *b)
        return i;
 }
 
+// Alternative with order A1, A2, B1, B2
+static int
+gnm_range_compare_alt (GnmRange const *a, GnmRange const *b)
+{
+       int i = 0;
+       if (!i) i = a->start.col - b->start.col;
+       if (!i) i = a->start.row - b->start.row;
+       if (!i) i = a->end.col - b->end.col;
+       if (!i) i = a->end.row - b->end.row;
+       return i;
+}
+
 
 GnmSheetRange *
 gnm_sheet_range_new (Sheet *sheet, GnmRange const *r)
@@ -1341,4 +1354,8 @@ gnm_range_simplify (GArray *arr)
                try_merge_pair (arr, ui - 1, ui);
        for (ui = arr->len - 1; ui > 0; ui--)
                try_merge_pair (arr, ui - 1, ui);
+
+       g_array_sort (arr, (GCompareFunc) gnm_range_compare_alt);
+       for (ui = arr->len - 1; ui > 0; ui--)
+               try_merge_pair (arr, ui - 1, ui);
 }
diff --git a/src/sheet-conditions.c b/src/sheet-conditions.c
new file mode 100644
index 000000000..5ee98a676
--- /dev/null
+++ b/src/sheet-conditions.c
@@ -0,0 +1,697 @@
+/*
+ * sheet-conditions.c: storage mechanism for conditional styles
+ *
+ * Copyright (C) 2020 Morten Welinder (terra gnome 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) version 3.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+ * USA
+ */
+
+// The style condition system's dependency setup
+//
+//
+//     GnmStyleCondDep #1 ------------+
+//                                    |
+//     GnmStyleCondDep #2 ------------+
+//                                    +-------- CSGroupDep
+//     GnmStyleCondDep #3 ------------+
+//                                    |
+//     GnmStyleCondDep #4 ------------+
+//
+// Each GnmStyleCondDep handles one GnmStyleCond with 0-2 condition expressions
+// in it.  It has a position (see below) and is hooked into the dependency for
+// just that single location.  The action of a GnmStyleCondDep being changed is
+// simply to mark its CSGroupDep being changed.  (This is not the main dependency
+// tracking mechanism.  However, when dynamic dependents are being created using
+// condition evaluations, they will be hooked here.)
+//
+// The CSGroupDep is handling all ranges for which a certain GnmStyleConditions
+// is in use (within a single sheet).  Its position and the position of all the
+// GnmStyleCondDep below it is the top left corner of the first range.  It
+// carries a single artificial expression consisting of ranges from all the
+// expressions in the GnmStyleCondDep extended to account for all ranges.
+// Example: style is for range C1:D9, so the position is C1.  Let the single
+// expression be A1>0 (relative to C1).  The artificial expression will be
+// A1:B9 and is the range that some cell in C1:C9 depends on.
+//
+// The dependency system will see A1 (irrelevant, for the GnmStyleCondDep) and
+// A1:B9 (for the CSGroupDep) which is what really triggers changes.
+
+
+#include <gnumeric-config.h>
+#include <sheet-conditions.h>
+#include <style-conditions.h>
+#include <sheet.h>
+#include <workbook-priv.h>
+#include <ranges.h>
+#include <expr.h>
+#include <expr-impl.h>
+#include <expr-name.h>
+#include <value.h>
+#include <func.h>
+#include <mstyle.h>
+#include <gutils.h>
+
+// Do house keeping at the end of loading instead of repeatedly during.
+// (Meant to be on; setting for debugging only.)
+#define FAST_LOAD 1
+
+// Short-circuit most work at exit time.
+// (Meant to be on; setting for debugging only.)
+#define FAST_EXIT 1
+
+static gboolean debug_sheet_conds;
+
+// ----------------------------------------------------------------------------
+
+static guint csgd_get_dep_type (void);
+
+typedef struct {
+       GnmDependent base;
+       GnmCellPos pos;
+} CSGroupDep;
+
+// A group is a collection of ranges that share conditional styling.  They
+// need not share GnmStyle.
+typedef struct {
+       // Dependent that gets triggered by the dependents managing the
+       // expressions inside the conditions.  (Must be first.)
+       CSGroupDep dep;
+
+       // The conditional styling
+       GnmStyleConditions *conds;
+
+       // The ranges
+       GArray *ranges; // element-type: GnmRange
+} CSGroup;
+
+struct GnmSheetConditionsData_ {
+       GHashTable *groups;
+       gboolean needs_simplify;
+
+       GHashTable *linked_conditions;
+
+       gulong sig_being_loaded;
+};
+
+static void update_group (CSGroup *g);
+
+static void
+cb_free_group (CSGroup *g)
+{
+       g_array_set_size (g->ranges, 0);
+       update_group (g);
+
+       g_array_free (g->ranges, TRUE);
+       g_free (g);
+}
+
+static void
+simplify_group (CSGroup *g)
+{
+       gnm_range_simplify (g->ranges);
+       update_group (g);
+}
+
+static gboolean
+sc_equal (GnmStyleConditions const *sca, GnmStyleConditions const *scb)
+{
+       return gnm_style_conditions_equal (sca, scb, FALSE);
+}
+
+static void
+cb_being_loaded (Sheet *sheet)
+{
+       if (!sheet->workbook->being_loaded)
+               sheet_conditions_simplify (sheet);
+}
+
+void
+sheet_conditions_init (Sheet *sheet)
+{
+       GnmSheetConditionsData *cd;
+
+       debug_sheet_conds = gnm_debug_flag ("sheet-conditions");
+
+       cd = sheet->conditions = g_new0 (GnmSheetConditionsData, 1);
+       cd->groups = g_hash_table_new_full
+               (g_direct_hash, g_direct_equal,
+                NULL,
+                (GDestroyNotify)cb_free_group);
+
+       cd->linked_conditions = g_hash_table_new
+               ((GHashFunc)gnm_style_conditions_hash,
+                (GCompareFunc)sc_equal);
+
+       cd->sig_being_loaded = sheet->workbook
+               ? g_signal_connect_swapped (G_OBJECT (sheet->workbook),
+                                           "notify::being-loaded",
+                                           G_CALLBACK (cb_being_loaded),
+                                           sheet)
+               : 0; // a preview grid sheet
+}
+
+
+void
+sheet_conditions_uninit (Sheet *sheet)
+{
+       GnmSheetConditionsData *cd = sheet->conditions;
+
+       if (cd->sig_being_loaded) {
+               g_signal_handler_disconnect (sheet->workbook, cd->sig_being_loaded);
+               cd->sig_being_loaded = 0;
+       }
+
+       if (g_hash_table_size (cd->groups) > 0)
+               g_warning ("Left-over conditional styling.");
+
+       g_hash_table_destroy (cd->groups);
+       cd->groups = NULL;
+
+       g_hash_table_destroy (cd->linked_conditions);
+       cd->linked_conditions = NULL;
+
+       g_free (cd);
+       sheet->conditions = NULL;
+}
+
+// ----------------------------------------------------------------------------
+
+/**
+ * sheet_conditions_share_conditions_add:
+ * @conds: (transfer none): 
+ *
+ * Returns: (transfer none) (nullable): Conditions equivalent to @conds, or
+ * %NULL if @conds had not been seen before.
+ */
+GnmStyleConditions *
+sheet_conditions_share_conditions_add (GnmStyleConditions *conds)
+{
+       Sheet *sheet = gnm_style_conditions_get_sheet (conds);
+       GnmSheetConditionsData *cd = sheet->conditions;
+       int n = 0;
+       GnmStyleConditions *res = NULL;
+       gpointer key, value;
+
+       if (g_hash_table_lookup_extended (cd->linked_conditions, conds, &key, &value)) {
+               res = conds = key;
+               n = GPOINTER_TO_INT (value);
+       }
+
+       g_hash_table_insert (cd->linked_conditions,
+                            conds,
+                            GINT_TO_POINTER (n + 1));
+       return res;
+}
+
+/**
+ * sheet_conditions_share_conditions_remove:
+ * @conds: (transfer none): 
+ *
+ * This notifies the sheet conditions manager that one use of the shared
+ * conditions has gone away.
+ */
+void
+sheet_conditions_share_conditions_remove (GnmStyleConditions *conds)
+{
+       Sheet *sheet = gnm_style_conditions_get_sheet (conds);
+       GnmSheetConditionsData *cd = sheet->conditions;
+       int n = GPOINTER_TO_INT (g_hash_table_lookup (cd->linked_conditions, conds));
+
+       if (n > 1)
+               g_hash_table_insert (cd->linked_conditions,
+                                    conds,
+                                    GINT_TO_POINTER (n - 1));
+       else if (n == 1)
+               g_hash_table_remove (cd->linked_conditions, conds);
+       else
+               g_warning ("We're confused with sheet condition usage.");
+}
+
+// ----------------------------------------------------------------------------
+
+void
+sheet_conditions_simplify (Sheet *sheet)
+{
+       GHashTableIter hiter;
+       gpointer value;
+       GnmSheetConditionsData *cd = sheet->conditions;
+
+       if (!cd->needs_simplify)
+               return;
+
+       if (debug_sheet_conds)
+               g_printerr ("Optimizing sheet conditions for %s\n",
+                           sheet->name_unquoted);
+
+       g_hash_table_iter_init (&hiter, cd->groups);
+       while (g_hash_table_iter_next (&hiter, NULL, &value)) {
+               CSGroup *g = value;
+               simplify_group (g);
+       }
+       cd->needs_simplify = FALSE;
+}
+
+void
+sheet_conditions_dump (Sheet *sheet)
+{
+       GnmSheetConditionsData *cd = sheet->conditions;
+       GHashTableIter hiter;
+       gpointer value;
+
+       g_printerr ("Conditional styling for sheet %s:\n", sheet->name_unquoted);
+       g_hash_table_iter_init (&hiter, cd->groups);
+       while (g_hash_table_iter_next (&hiter, NULL, &value)) {
+               CSGroup *g = value;
+               unsigned ui, ri;
+               GPtrArray const *ga;
+               GnmCellPos const *pos;
+
+               pos = gnm_style_conditions_get_pos (g->conds);
+               g_printerr ("  Conditions at %s\n",
+                           pos ? cellpos_as_string (pos) : "-");
+               ga = gnm_style_conditions_details (g->conds);
+               for (ui = 0; ui < (ga ? ga->len : 0u); ui++) {
+                       GnmStyleCond *sc = g_ptr_array_index (ga, ui);
+                       char *s = gnm_style_cond_as_string (sc);
+                       g_printerr ("    [%d] %s\n", ui, s);
+                       g_free (s);
+               }
+
+               g_printerr ("  Ranges:\n");
+               for (ri = 0; ri < g->ranges->len; ri++) {
+                       GnmRange *r = &g_array_index (g->ranges, GnmRange, ri);
+                       g_printerr ("    [%d] %s\n", ri, range_as_string (r));
+               }
+       }
+}
+
+// ----------------------------------------------------------------------------
+
+static CSGroup *
+find_group (GnmSheetConditionsData *cd, GnmStyle *style)
+{
+       GnmStyleConditions const *conds = gnm_style_get_conditions (style);
+       return g_hash_table_lookup (cd->groups, conds);
+}
+
+
+void
+sheet_conditions_add (Sheet *sheet, GnmRange const *r, GnmStyle *style)
+{
+       GnmSheetConditionsData *cd = sheet->conditions;
+       CSGroup *g;
+
+       if (FAST_EXIT && sheet->being_destructed)
+               return;
+
+       g = find_group (cd, style);
+       if (!g) {
+               g = g_new0 (CSGroup, 1);
+               g->dep.base.flags = csgd_get_dep_type ();
+               g->dep.base.sheet = sheet;
+               g->conds = gnm_style_get_conditions (style);
+               g->ranges = g_array_new (FALSE, FALSE, sizeof (GnmRange));
+               g_hash_table_insert (cd->groups, g->conds, g);
+       }
+
+       g_array_append_val (g->ranges, *r);
+       if (g->ranges->len > 1) {
+               if (FAST_LOAD && sheet->workbook->being_loaded)
+                       cd->needs_simplify = TRUE;
+               else
+                       simplify_group (g);
+       } else
+               update_group (g);
+}
+
+void
+sheet_conditions_remove (Sheet *sheet, GnmRange const *r, GnmStyle *style)
+{
+       GnmSheetConditionsData *cd = sheet->conditions;
+       CSGroup *g;
+       unsigned ri;
+
+       if (FAST_EXIT && sheet->being_destructed) {
+               g_hash_table_remove_all (cd->groups);
+               return;
+       }
+
+       g = find_group (cd, style);
+       if (!g) {
+               g_warning ("Removing conditional style we don't have?");
+               return;
+       }
+
+       for (ri = 0; ri < g->ranges->len; ri++) {
+               GnmRange *r2 = &g_array_index (g->ranges, GnmRange, ri);
+               GnmRange rest[4];
+               int n = 0;
+
+               if (!range_overlap (r, r2))
+                       continue;
+
+               if (r->start.col > r2->start.col) {
+                       // Keep a section to the left
+                       rest[n] = *r2;
+                       rest[n].end.col = r->start.col - 1;
+                       n++;
+               }
+               if (r->end.col < r2->end.col) {
+                       // Keep a section to the right
+                       rest[n] = *r2;
+                       rest[n].start.col = r->end.col + 1;
+                       n++;
+               }
+               if (r->start.row > r2->start.row) {
+                       // Keep a section above
+                       rest[n] = *r2;
+                       rest[n].end.row = r->start.row - 1;
+                       n++;
+                       }
+               if (r->end.row < r2->end.row) {
+                       // Keep a section below
+                       rest[n] = *r2;
+                       rest[n].start.row = r->end.row + 1;
+                       n++;
+               }
+
+               if (n == 0) {
+                       g_array_remove_index (g->ranges, ri);
+                       ri--;  // Counter-act loop increment
+                       if (g->ranges->len == 0) {
+                               g_hash_table_remove (cd->groups, g->conds);
+                               g = NULL;
+                               break;
+                       }
+               } else {
+                       *r2 = rest[0];
+                       g_array_append_vals (g->ranges, rest + 1, n - 1);
+               }
+       }
+
+       if (FAST_LOAD && sheet->workbook->being_loaded)
+               cd->needs_simplify = TRUE;
+       else if (g)
+               simplify_group (g);
+}
+
+// ----------------------------------------------------------------------------
+
+static void
+set_group_pos_and_expr (CSGroup *g, const GnmCellPos *pos, GnmExprTop const *texpr)
+{
+       GnmDependent *dep = &g->dep.base;
+
+       if (dependent_is_linked (dep))
+               dependent_unlink (dep);
+       if (texpr != dep->texpr)
+               dependent_set_expr (dep, texpr);
+       g->dep.pos = *pos;
+       if (texpr)
+               dependent_link (dep);
+}
+
+typedef struct {
+       GnmEvalPos epos;
+       GnmExprList *deps;
+       GnmRange const *r;
+       Sheet *sheet;
+} CollectGroupDepsState;
+
+typedef enum {
+       CGD_NO_FLAGS = 0,
+       CGD_NON_SCALAR = 1,
+} CollectGroupDefsFlags;
+
+
+static void
+collect_group_deps_rr (GnmRangeRef const *rr, CollectGroupDepsState *state,
+                      CollectGroupDefsFlags flags)
+{
+       GnmRangeRef rr1, rr2;
+       Sheet *a_sheet = eval_sheet (rr->a.sheet, state->sheet);
+       Sheet *b_sheet = eval_sheet (rr->b.sheet, a_sheet);
+       GnmRange r;
+       int col, row;
+       gboolean found = FALSE;
+       int W = range_width (state->r);
+       int H = range_height (state->r);
+
+       if (a_sheet == state->sheet &&
+           rr->a.col_relative && rr->a.col == 0 &&
+           rr->a.row_relative && rr->a.row == 0 &&
+           b_sheet == state->sheet &&
+           rr->b.col_relative && rr->b.col == 0 &&
+           rr->b.row_relative && rr->b.row == 0) {
+               // Ignore references to the cell itself -- the recalc
+               // dependency is enought to update everything.
+               if (debug_sheet_conds)
+                       g_printerr ("Self reference\n");
+               return;
+       }
+
+       // Inspiration from value_intersection:
+       gnm_rangeref_normalize (rr, &state->epos, &a_sheet, &b_sheet, &r);
+
+       if (flags & CGD_NON_SCALAR)
+               goto everything;
+
+       if (eval_pos_is_array_context (&state->epos))
+               goto everything; // Potential implicit iteration -- bail
+
+       if (!(a_sheet == b_sheet || b_sheet == NULL))
+               return; // An error
+
+       col = state->epos.eval.col;
+       row = state->epos.eval.row;
+
+       if (range_is_singleton (&r)) {
+               col = r.start.col;
+               row = r.start.row;
+               found = TRUE;
+       } else if (r.start.row == r.end.row &&
+                  r.start.col <= col && col + (W - 1) <= r.end.col) {
+               row = r.start.row;
+               found = TRUE;
+       } else if (r.start.col == r.end.col &&
+                  r.start.row <= row && row + (H - 1) <= r.end.row) {
+               col = r.start.col;
+               found = TRUE;
+       }
+       if (!found)
+               goto everything;
+
+       // Intersection.  The range is equivalent to a single cell
+       gnm_cellref_init (&rr1.a, a_sheet, col, row, FALSE);
+       rr1.b = rr1.a;
+       rr = &rr1;
+
+everything:
+       if (!(a_sheet == b_sheet || b_sheet == NULL)) {
+               if (debug_sheet_conds)
+                       g_printerr ("Ignoring 3d reference for conditional style.\n");
+               return;
+       }
+
+       // Ignore wrapping for now.
+       rr2 = *rr;
+       rr2.b.col += W - 1;
+       rr2.b.row += H - 1;
+
+       state->deps = gnm_expr_list_prepend
+               (state->deps,
+                gnm_expr_new_constant
+                (value_new_cellrange_unsafe (&rr2.a, &rr2.b)));
+}
+
+static void
+collect_group_deps (GnmExpr const *expr, CollectGroupDepsState *state,
+                   CollectGroupDefsFlags flags)
+{
+       switch (GNM_EXPR_GET_OPER (expr)) {
+       case GNM_EXPR_OP_CONSTANT: {
+               GnmValue const *cst = gnm_expr_get_constant (expr);
+               if (VALUE_IS_CELLRANGE (cst))
+                       collect_group_deps_rr (value_get_rangeref (cst),
+                                              state, flags);
+               return;
+       }
+
+       case GNM_EXPR_OP_NAME: {
+               GnmNamedExpr const *nexpr = gnm_expr_get_name (expr);
+               state->deps = gnm_expr_list_prepend
+                       (state->deps,
+                        gnm_expr_copy (expr));
+               if (expr_name_is_active (nexpr))
+                       collect_group_deps (nexpr->texpr->expr, state, flags);
+               return;
+       }
+
+       case GNM_EXPR_OP_CELLREF: {
+               GnmCellRef const *cr = gnm_expr_get_cellref (expr);
+               GnmRangeRef rr;
+               rr.a = *cr;
+               rr.b = *cr;
+               rr.b.sheet = NULL;
+               collect_group_deps_rr (&rr, state, flags);
+               return;
+       }
+       }
+
+       // Otherwise... descend into subexpressions
+       switch (GNM_EXPR_GET_OPER (expr)) {
+       case GNM_EXPR_OP_RANGE_CTOR:
+       case GNM_EXPR_OP_INTERSECT:
+               collect_group_deps (expr->binary.value_a, state, flags);
+               collect_group_deps (expr->binary.value_b, state, flags);
+               return;
+
+       case GNM_EXPR_OP_ANY_BINARY:
+               if (!eval_pos_is_array_context (&state->epos))
+                       flags &= ~CGD_NON_SCALAR;
+               collect_group_deps (expr->binary.value_a, state, flags);
+               collect_group_deps (expr->binary.value_b, state, flags);
+               return;
+
+       case GNM_EXPR_OP_ANY_UNARY:
+               if (!eval_pos_is_array_context (&state->epos))
+                       flags &= ~CGD_NON_SCALAR;
+               collect_group_deps (expr->unary.value, state, flags);
+               return;
+
+       case GNM_EXPR_OP_FUNCALL: {
+               GnmExprFunction const *call = &expr->func;
+               GnmFunc const *func = call->func;
+               int i, argc = call->argc;
+               CollectGroupDefsFlags pass = flags & CGD_NO_FLAGS;
+
+               if (gnm_func_get_flags (call->func) & GNM_FUNC_IS_PLACEHOLDER)
+                       break;
+
+               for (i = 0; i < argc; i++) {
+                       char t = gnm_func_get_arg_type (func, i);
+                       CollectGroupDefsFlags extra =
+                               (t == 'A' || t == 'r' || t == '?')
+                               ? CGD_NON_SCALAR
+                               : CGD_NO_FLAGS;
+                       collect_group_deps (expr->func.argv[i], state,
+                                           pass | extra);
+               }
+               return;
+       }
+
+       case GNM_EXPR_OP_SET: {
+               int i, argc = expr->set.argc;
+               for (i = 0; i < argc; i++)
+                       collect_group_deps (expr->set.argv[i], state, flags);
+               return;
+       }
+
+       case GNM_EXPR_OP_ARRAY_CORNER:
+               collect_group_deps (expr->array_corner.expr, state,
+                                   flags | CGD_NON_SCALAR);
+               return;
+
+       default:
+               return;
+       }
+}
+
+
+
+static void
+update_group (CSGroup *g)
+{
+       GnmCellPos const *pos;
+       GnmExprTop const *texpr;
+       GPtrArray const *ga;
+       CollectGroupDepsState state;
+       unsigned ui;
+
+       if (g->ranges->len == 0) {
+               dependent_set_expr (&g->dep.base, NULL);
+               return;
+       }
+
+       pos = &g_array_index (g->ranges, GnmRange, 0).start;
+       gnm_style_conditions_set_pos (g->conds, pos);
+
+       state.deps = NULL;
+       state.sheet = g->dep.base.sheet;
+       ga = gnm_style_conditions_details (g->conds);
+       for (ui = 0; ui < (ga ? ga->len : 0u); ui++) {
+               GnmStyleCond *cond = g_ptr_array_index (ga, ui);
+               unsigned ix;
+               for (ix = 0; ix < G_N_ELEMENTS (cond->deps); ix++) {
+                       GnmExprTop const *te = gnm_style_cond_get_expr (cond, ix);
+                       if (te) {
+                               unsigned ri;
+                               eval_pos_init_dep (&state.epos, &cond->deps[ix].base);
+                               for (ri = 0; ri < g->ranges->len; ri++) {
+                                       state.r = &g_array_index (g->ranges, GnmRange, ri);
+                                       collect_group_deps (te->expr, &state, CGD_NO_FLAGS);
+                               }
+                       }
+               }
+       }
+       texpr = state.deps
+               ? gnm_expr_top_new (gnm_expr_new_set (state.deps))
+               : gnm_expr_top_new_constant (value_new_error_REF (NULL));
+       set_group_pos_and_expr (g, pos, texpr);
+       gnm_expr_top_unref (texpr);
+}
+
+// ----------------------------------------------------------------------------
+
+static void
+csgd_eval (GnmDependent *dep)
+{
+       // Nothing yet
+}
+
+static GSList *
+csgd_changed (GnmDependent *dep)
+{
+       CSGroupDep *gd = (CSGroupDep *)dep;
+       CSGroup *g = (CSGroup *)gd; // Since the dep is first
+       Sheet *sheet = dep->sheet;
+       unsigned ri;
+
+       for (ri = 0; ri < g->ranges->len; ri++) {
+               GnmRange *r = &g_array_index (g->ranges, GnmRange, ri);
+               // FIXME:
+               // sheet_range_calc_spans ???
+               // or other unrender
+               sheet_queue_redraw_range (sheet, r);
+       }
+
+       return NULL;
+}
+
+static GnmCellPos *
+csgd_pos (GnmDependent const *dep)
+{
+       return &((CSGroupDep *)dep)->pos;
+}
+
+static void
+csgd_debug_name (GnmDependent const *dep, GString *target)
+{
+       g_string_append_printf (target, "CSGroup/%p", (void *)dep);
+}
+
+
+static DEPENDENT_MAKE_TYPE(csgd, .eval = csgd_eval, .changed = csgd_changed, .pos = csgd_pos, .debug_name =  
csgd_debug_name)
diff --git a/src/sheet-conditions.h b/src/sheet-conditions.h
new file mode 100644
index 000000000..a2ac40030
--- /dev/null
+++ b/src/sheet-conditions.h
@@ -0,0 +1,23 @@
+#ifndef GNM_SHEET_CONDITIONS_H_
+#define GNM_SHEET_CONDITIONS_H_
+
+#include <gnumeric.h>
+
+G_BEGIN_DECLS
+
+void sheet_conditions_init (Sheet *sheet);
+void sheet_conditions_uninit (Sheet *sheet);
+// resize?
+
+GnmStyleConditions *sheet_conditions_share_conditions_add (GnmStyleConditions *conds);
+void sheet_conditions_share_conditions_remove (GnmStyleConditions *conds);
+
+void sheet_conditions_add (Sheet *sheet, GnmRange const *r, GnmStyle *style);
+void sheet_conditions_remove (Sheet *sheet, GnmRange const *r, GnmStyle *style);
+
+void sheet_conditions_simplify (Sheet *sheet);
+void sheet_conditions_dump (Sheet *sheet);
+
+G_END_DECLS
+
+#endif
diff --git a/src/sheet.c b/src/sheet.c
index fb7f7684a..a0701b744 100644
--- a/src/sheet.c
+++ b/src/sheet.c
@@ -28,6 +28,7 @@
 #include <command-context.h>
 #include <sheet-control.h>
 #include <sheet-style.h>
+#include <sheet-conditions.h>
 #include <workbook-priv.h>
 #include <workbook-control.h>
 #include <workbook-view.h>
@@ -709,6 +710,8 @@ gnm_sheet_constructed (GObject *obj)
        range_init_full_sheet (&sheet->priv->unhidden_region, sheet);
        sheet_style_init (sheet);
 
+       sheet_conditions_init (sheet);
+
        sheet->deps = gnm_dep_container_new (sheet);
 
        switch (sheet->sheet_type) {
@@ -4964,6 +4967,7 @@ gnm_sheet_finalize (GObject *obj)
        }
 
        sheet_style_shutdown (sheet);
+       sheet_conditions_uninit (sheet);
 
        if (sheet->pending_redraw_src) {
                g_source_remove (sheet->pending_redraw_src);
diff --git a/src/sheet.h b/src/sheet.h
index 77e0f08b2..00693245f 100644
--- a/src/sheet.h
+++ b/src/sheet.h
@@ -49,6 +49,8 @@ struct _Sheet {
 
        GnmSheetStyleData *style_data; /* See sheet-style.c */
 
+       GnmSheetConditionsData *conditions; // See sheet-conditions.c
+
        ColRowCollection cols, rows;
 
        GHashTable  *cell_hash; /* The cells in hashed format */
diff --git a/src/style-conditions.c b/src/style-conditions.c
index c14b6eec3..3f8f969c2 100644
--- a/src/style-conditions.c
+++ b/src/style-conditions.c
@@ -28,7 +28,6 @@
 #include <cell.h>
 #include <value.h>
 #include <sheet.h>
-#include <parse-util.h>
 #include <gsf/gsf-impl-utils.h>
 #include <string.h>
 #include <func.h>
@@ -52,6 +51,12 @@ debug_style_conds (void)
        return debug;
 }
 
+// ----------------------------------------------------------------------------
+
+static guint gscd_get_dep_type (void);
+
+// ----------------------------------------------------------------------------
+
 static unsigned
 gnm_style_cond_op_operands (GnmStyleCondOp op)
 {
@@ -84,7 +89,6 @@ gnm_style_cond_op_operands (GnmStyleCondOp op)
        g_assert_not_reached ();
 }
 
-
 /**
  * gnm_style_cond_is_valid:
  * @cond: #GnmStyleCond
@@ -107,7 +111,7 @@ gnm_style_cond_is_valid (GnmStyleCond const *cond)
        N = gnm_style_cond_op_operands (cond->op);
        for (ui = 0; ui < G_N_ELEMENTS (cond->deps); ui++) {
                gboolean need = (ui < N);
-               gboolean have = (dependent_managed_get_expr (&cond->deps[ui]) != NULL);
+               gboolean have = (cond->deps[ui].base.texpr != NULL);
                if (have != need)
                        return FALSE;
        }
@@ -125,8 +129,10 @@ gnm_style_cond_new (GnmStyleCondOp op, Sheet *sheet)
 
        res = g_new0 (GnmStyleCond, 1);
        res->op = op;
-       for (ui = 0; ui < 2; ui++)
-               dependent_managed_init (&res->deps[ui], sheet);
+       for (ui = 0; ui < 2; ui++) {
+               res->deps[ui].base.flags = gscd_get_dep_type ();
+               res->deps[ui].base.sheet = sheet;
+       }
        return res;
 }
 
@@ -148,7 +154,7 @@ gnm_style_cond_dup_to (GnmStyleCond const *src, Sheet *sheet)
        dst = gnm_style_cond_new (src->op, sheet);
        gnm_style_cond_set_overlay (dst, src->overlay);
        for (ui = 0; ui < 2; ui++)
-               gnm_style_cond_set_expr (dst, dependent_managed_get_expr (&src->deps[ui]), ui);
+               gnm_style_cond_set_expr (dst, src->deps[ui].base.texpr, ui);
 
        return dst;
 }
@@ -222,7 +228,7 @@ gnm_style_cond_get_expr (GnmStyleCond const *cond, unsigned idx)
        g_return_val_if_fail (cond != NULL, NULL);
        g_return_val_if_fail (idx < G_N_ELEMENTS (cond->deps), NULL);
 
-       return dependent_managed_get_expr (&cond->deps[idx]);
+       return cond->deps[idx].base.texpr;
 }
 
 void
@@ -233,7 +239,9 @@ gnm_style_cond_set_expr (GnmStyleCond *cond,
        g_return_if_fail (cond != NULL);
        g_return_if_fail (idx < G_N_ELEMENTS (cond->deps));
 
-       dependent_managed_set_expr (&cond->deps[idx], texpr);
+       dependent_set_expr (&cond->deps[idx].base, texpr);
+       if (texpr)
+               dependent_link (&cond->deps[idx].base);
 }
 
 void
@@ -559,21 +567,24 @@ case_insensitive_has_fix (GnmValue const *vs, GnmValue const *vp,
 
 
 static gboolean
-gnm_style_cond_eval (GnmStyleCond const *cond, GnmValue const *cv,
+gnm_style_cond_eval (GnmStyleCond *cond, GnmValue const *cv,
                     GnmEvalPos const *ep)
 {
        gboolean negate = FALSE;
        gboolean res;
        GnmValue *val0 = NULL;
        GnmValue *val1 = NULL;
+       GnmEvalPos epos = *ep;
 
        switch (gnm_style_cond_op_operands (cond->op)) {
        case 2:
-               val1 = gnm_expr_top_eval (cond->deps[1].base.texpr, ep,
+               epos.dep = &cond->deps[1].base;
+               val1 = gnm_expr_top_eval (cond->deps[1].base.texpr, &epos,
                                          GNM_EXPR_EVAL_SCALAR_NON_EMPTY);
                /* Fall through */
        case 1:
-               val0 = gnm_expr_top_eval (cond->deps[0].base.texpr, ep,
+               epos.dep = &cond->deps[0].base;
+               val0 = gnm_expr_top_eval (cond->deps[0].base.texpr, &epos,
                                          GNM_EXPR_EVAL_SCALAR_NON_EMPTY);
                /* Fall through */
        case 0:
@@ -687,6 +698,91 @@ gnm_style_cond_equal (GnmStyleCond const *ca, GnmStyleCond const *cb,
        return TRUE;
 }
 
+static void
+gnm_style_cond_set_pos (GnmStyleCond *sc, GnmCellPos const *pos)
+{
+       unsigned oi, N;
+
+       N = gnm_style_cond_op_operands (sc->op);
+       for (oi = 0; oi < N; oi++) {
+               gboolean qlink = dependent_is_linked (&sc->deps[oi].base);
+               if (qlink)
+                       dependent_unlink (&sc->deps[oi].base);
+               sc->deps[oi].pos = *pos;
+               if (qlink)
+                       dependent_link (&sc->deps[oi].base);
+       }
+}
+
+// For debugging purposes
+char *
+gnm_style_cond_as_string (GnmStyleCond const *cond)
+{
+       unsigned oi, N;
+       static const char * const ops[] = {
+               "between", "not-between",
+               "equal", "not-equal",
+               "greater-than", "less-then",
+               "greater-than-or-equal", "less-than-or-equal",
+               "is-true",
+               NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+               "contains", "does-not-contain",
+               "begins-with", "does-not-begin-with",
+               "end-with", "does-not-end-with",
+               "is-error", "is-not-error",
+               "contains-blank", "does-not-contain-blank"
+       };
+       GString *str = g_string_new (ops[cond->op]);
+       Sheet *sheet = gnm_style_cond_get_sheet (cond);
+       GnmConventions const *convs = sheet_get_conventions (sheet);
+
+       N = gnm_style_cond_op_operands (cond->op);
+       for (oi = 0; oi < N; oi++) {
+               char *s;
+               GnmParsePos pp;
+
+               parse_pos_init_dep (&pp, &cond->deps[oi].base);
+               s = gnm_expr_top_as_string (gnm_style_cond_get_expr (cond, oi),
+                                           &pp,
+                                           convs);
+               g_string_append_c (str, ' ');
+               g_string_append (str, s);
+               g_free (s);
+       }
+       return g_string_free (str, FALSE);
+}
+
+// ----------------------------------------------------------------------------
+
+static void
+gscd_eval (GnmDependent *dep)
+{
+       // Nothing yet
+}
+
+static GSList *
+gscd_changed (GnmDependent *dep)
+{
+       GnmStyleCondDep const *scd = (GnmStyleCondDep const *)dep;
+       return scd->dep_cont ? g_slist_prepend (NULL, scd->dep_cont) : NULL;
+}
+
+static GnmCellPos *
+gscd_pos (GnmDependent const *dep)
+{
+       return &((GnmStyleCondDep *)dep)->pos;
+}
+
+static void
+gscd_debug_name (GnmDependent const *dep, GString *target)
+{
+       g_string_append_printf (target, "StyleCondDep/%p", (void *)dep);
+}
+
+
+static DEPENDENT_MAKE_TYPE(gscd, .eval = gscd_eval, .changed = gscd_changed, .pos = gscd_pos, .debug_name =  
gscd_debug_name)
+
+// ----------------------------------------------------------------------------
 
 static void
 gnm_style_conditions_finalize (GObject *obj)
@@ -1001,7 +1097,7 @@ gnm_style_conditions_eval (GnmStyleConditions const *sc, GnmEvalPos const *ep)
        }
 
        for (i = 0 ; i < conds->len ; i++) {
-               GnmStyleCond const *cond = g_ptr_array_index (conds, i);
+               GnmStyleCond *cond = g_ptr_array_index (conds, i);
                gboolean use_this = gnm_style_cond_eval (cond, cv, ep);
 
                if (use_this) {
@@ -1018,3 +1114,54 @@ gnm_style_conditions_eval (GnmStyleConditions const *sc, GnmEvalPos const *ep)
        value_release (cv);
        return -1;
 }
+
+
+/**
+ * gnm_style_conditions_set_pos:
+ * @sc: #GnmStyleConditions
+ * @pos: new position
+ *
+ * Sets the position of @sc, i.e., the position at which relative addresses
+ * in the conditions will be evaluated.
+ **/
+void
+gnm_style_conditions_set_pos (GnmStyleConditions *sc,
+                             GnmCellPos const *pos)
+{
+       GPtrArray const *ga;
+       unsigned ui;
+
+       g_return_if_fail (sc != NULL);
+
+       ga = gnm_style_conditions_details (sc);
+       for (ui = 0; ui < (ga ? ga->len : 0u); ui++) {
+               GnmStyleCond *cond = g_ptr_array_index (ga, ui);
+               gnm_style_cond_set_pos (cond, pos);
+       }
+}
+
+/**
+ * gnm_style_conditions_get_pos:
+ * @sc: #GnmStyleConditions
+ *
+ * Returns: (transfer none) (nullable): The position at which relative
+ * addresses in the conditions will be evaluated.  This may be %NULL if
+ * no conditions require a position.
+ **/
+GnmCellPos const *
+gnm_style_conditions_get_pos (GnmStyleConditions const *sc)
+{
+       GPtrArray const *ga;
+       unsigned ui;
+
+       g_return_val_if_fail (sc != NULL, NULL);
+
+       ga = gnm_style_conditions_details (sc);
+       for (ui = 0; ui < (ga ? ga->len : 0u); ui++) {
+               GnmStyleCond *cond = g_ptr_array_index (ga, ui);
+               int N = gnm_style_cond_op_operands (cond->op);
+               if (N > 0)
+                       return dependent_pos (&cond->deps[0].base);
+       }
+       return NULL;
+}
diff --git a/src/style-conditions.h b/src/style-conditions.h
index 5297c9e52..92d839153 100644
--- a/src/style-conditions.h
+++ b/src/style-conditions.h
@@ -6,7 +6,8 @@
 
 G_BEGIN_DECLS
 
-/* This is persisted directly in .gnumeric files, DO NOT REMOVE OR REORDER */
+// The values here are persisted directly in .gnumeric files as numbers.
+// DO NOT REMOVE OR REORDER ANYTHING
 typedef enum {
        /* Cell Value */
        GNM_STYLE_COND_BETWEEN,
@@ -36,9 +37,15 @@ typedef enum {
        GNM_STYLE_COND_NOT_CONTAINS_BLANKS
 } GnmStyleCondOp;
 
+typedef struct {
+       GnmDependent base;
+       GnmCellPos pos;
+       GnmDependent *dep_cont;
+} GnmStyleCondDep;
+
 typedef struct {
        GnmStyle         *overlay;
-       GnmDepManaged     deps[2];
+       GnmStyleCondDep   deps[2];
        GnmStyleCondOp    op;
 } GnmStyleCond;
 
@@ -60,6 +67,10 @@ GnmExprTop const *gnm_style_cond_get_alternate_expr (GnmStyleCond const *cond);
 void gnm_style_cond_canonicalize (GnmStyleCond *cond);
 
 Sheet      *gnm_style_cond_get_sheet (GnmStyleCond const *cond);
+char       *gnm_style_cond_as_string (GnmStyleCond const *cond);
+
+
+
 
 GType         gnm_style_conditions_get_type (void);
 GnmStyleConditions *gnm_style_conditions_new  (Sheet *sheet);
@@ -84,6 +95,10 @@ gboolean    gnm_style_conditions_equal     (GnmStyleConditions const *sca,
                                            GnmStyleConditions const *scb,
                                            gboolean relax_sheet);
 
+GnmCellPos const *gnm_style_conditions_get_pos   (GnmStyleConditions const *sc);
+void        gnm_style_conditions_set_pos   (GnmStyleConditions *sc,
+                                           GnmCellPos const *pos);
+
 G_END_DECLS
 
 #endif /* _GNM_STYLE_CONDITIONS_H_ */
diff --git a/src/wbc-gtk.c b/src/wbc-gtk.c
index d73dff2b9..d3cc16947 100644
--- a/src/wbc-gtk.c
+++ b/src/wbc-gtk.c
@@ -33,6 +33,7 @@
 #include <sheet-private.h>
 #include <sheet-view.h>
 #include <sheet-style.h>
+#include <sheet-conditions.h>
 #include <sheet-filter.h>
 #include <commands.h>
 #include <dependent.h>
@@ -2240,6 +2241,12 @@ cb_workbook_debug_info (WBCGtk *wbcg)
                workbook_optimize_style (wb);
        }
 
+       if (gnm_debug_flag ("sheet-conditions")) {
+               WORKBOOK_FOREACH_SHEET(wb, sheet, {
+                       sheet_conditions_dump (sheet);
+               });
+       }
+
        if (gnm_debug_flag ("name-collections")) {
                gnm_named_expr_collection_dump (wb->names, "workbook");
                WORKBOOK_FOREACH_SHEET(wb, sheet, {
@@ -2847,6 +2854,7 @@ wbc_gtk_create_edit_area (WBCGtk *wbcg)
            gnm_debug_flag ("deps") ||
            gnm_debug_flag ("expr-sharer") ||
            gnm_debug_flag ("style-optimize") ||
+           gnm_debug_flag ("sheet-conditions") ||
            gnm_debug_flag ("name-collections")) {
                g_signal_connect_swapped (debug_button,
                                          "clicked", G_CALLBACK (cb_workbook_debug_info),



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