1 /*
2  * gnm-sheet-slicer.c:
3  *
4  * Copyright (C) 2008-2009 Jody Goldberg (jody@gnome.org)
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of the
9  * License, or (at your option) version 3.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
19  * USA
20  */
21 
22 #include <gnumeric-config.h>
23 #include <gnm-sheet-slicer.h>
24 #include <go-data-slicer-impl.h>
25 #include <go-data-slicer-field-impl.h>
26 #include <go-data-cache.h>
27 #include <sheet.h>
28 #include <ranges.h>
29 
30 #include <gsf/gsf-impl-utils.h>
31 #include <glib/gi18n-lib.h>
32 #include <string.h>
33 
34 #include <glib-object.h>
35 
36 struct _GnmSheetSlicer {
37 	GODataSlicer		base;
38 
39 	Sheet		*sheet;
40 	GnmRange	 range;
41 
42 	/* Offsets from the top-left (in LTR) pos range */
43 	unsigned int	 first_header_row, first_data_row, first_data_col;
44 	unsigned int	 row_page_count, col_page_count;
45 
46 	struct {
47 		gboolean headers_col, headers_row, stripes_col, stripes_row, last_col, last_row;
48 	} show;
49 
50 	GnmSheetSlicerLayout	layout;
51 };
52 typedef GODataSlicerClass GnmSheetSlicerClass;
53 
54 #define GNM_SHEET_SLICER_CLASS(k)	(G_TYPE_CHECK_CLASS_CAST ((k), GNM_SHEET_SLICER_TYPE, GnmSheetSlicerClass))
55 #define GNM_IS_SHEET_SLICER_CLASS(k)	(G_TYPE_CHECK_CLASS_TYPE ((k), GNM_SHEET_SLICER_TYPE))
56 #define GNM_SHEET_SLICER_GET_CLASS(o)	(G_TYPE_INSTANCE_GET_CLASS ((o), GNM_SHEET_SLICER_TYPE, GnmSheetSlicerClass))
57 
58 enum {
59 	PROP_0,
60 	PROP_SHEET,
61 	PROP_RANGE,
62 
63 	PROP_FIRST_HEADER_ROW,
64 	PROP_FIRST_DATA_COL,
65 	PROP_FIRST_DATA_ROW,
66 
67 	PROP_SHOW_HEADERS_COL,
68 	PROP_SHOW_HEADERS_ROW,
69 	PROP_SHOW_STRIPES_COL,
70 	PROP_SHOW_STRIPES_ROW,
71 	PROP_SHOW_LAST_COL,
72 	PROP_SHOW_LAST_ROW,
73 
74 	PROP_LAYOUT
75 };
76 
77 static GObjectClass *parent_klass;
78 static void
gnm_sheet_slicer_init(GnmSheetSlicer * gss)79 gnm_sheet_slicer_init (GnmSheetSlicer *gss)
80 {
81 	gss->sheet = NULL;
82 	gss->first_header_row = gss->first_data_row = gss->first_data_col = gss->row_page_count = gss->col_page_count = 0;
83 }
84 
85 static void
gnm_sheet_slicer_finalize(GObject * obj)86 gnm_sheet_slicer_finalize (GObject *obj)
87 {
88 	GnmSheetSlicer *gss = (GnmSheetSlicer *)obj;
89 
90 	if (NULL != gss->sheet) {
91 		g_warning ("finalizing a slicer that is still attached to a sheet");
92 	}
93 
94 	(parent_klass->finalize) (obj);
95 }
96 
97 static void
gnm_sheet_slicer_set_property(GObject * obj,guint property_id,GValue const * value,GParamSpec * pspec)98 gnm_sheet_slicer_set_property (GObject *obj, guint property_id,
99 			       GValue const *value, GParamSpec *pspec)
100 {
101 	GnmSheetSlicer *gss = (GnmSheetSlicer *)obj;
102 
103 	switch (property_id) {
104 	case PROP_SHEET : gnm_sheet_slicer_set_sheet (gss, g_value_get_object (value)); break;
105 	case PROP_RANGE : gnm_sheet_slicer_set_range (gss, g_value_get_boxed (value)); break;
106 	case PROP_FIRST_HEADER_ROW : gss->first_header_row = g_value_get_uint (value); break;
107 	case PROP_FIRST_DATA_COL : gss->first_data_col = g_value_get_uint (value); break;
108 	case PROP_FIRST_DATA_ROW : gss->first_data_row = g_value_get_uint (value); break;
109 
110 	case PROP_SHOW_HEADERS_COL : gss->show.headers_col = g_value_get_boolean (value); break;
111 	case PROP_SHOW_HEADERS_ROW : gss->show.headers_row = g_value_get_boolean (value); break;
112 	case PROP_SHOW_STRIPES_COL : gss->show.stripes_col = g_value_get_boolean (value); break;
113 	case PROP_SHOW_STRIPES_ROW : gss->show.stripes_row = g_value_get_boolean (value); break;
114 	case PROP_SHOW_LAST_COL    : gss->show.last_col    = g_value_get_boolean (value); break;
115 	case PROP_SHOW_LAST_ROW    : gss->show.last_row    = g_value_get_boolean (value); break;
116 
117 	case PROP_LAYOUT: gnm_sheet_slicer_set_layout (gss, g_value_get_enum (value)); break;
118 	default:
119 		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec);
120 	}
121 }
122 
123 static void
gnm_sheet_slicer_get_property(GObject * obj,guint property_id,GValue * value,GParamSpec * pspec)124 gnm_sheet_slicer_get_property (GObject *obj, guint property_id,
125 			       GValue *value, GParamSpec *pspec)
126 {
127 	GnmSheetSlicer const *gss = (GnmSheetSlicer const *)obj;
128 	switch (property_id) {
129 	case PROP_SHEET : g_value_set_object (value, gss->sheet); break;
130 	case PROP_RANGE : g_value_set_boxed (value, &gss->range); break;
131 	case PROP_FIRST_HEADER_ROW : g_value_set_uint (value, gss->first_header_row ); break;
132 	case PROP_FIRST_DATA_COL : g_value_set_uint (value, gss->first_data_col); break;
133 	case PROP_FIRST_DATA_ROW : g_value_set_uint (value, gss->first_data_row); break;
134 
135 	case PROP_SHOW_HEADERS_COL : g_value_set_boolean (value, gss->show.headers_col); break;
136 	case PROP_SHOW_HEADERS_ROW : g_value_set_boolean (value, gss->show.headers_row); break;
137 	case PROP_SHOW_STRIPES_COL : g_value_set_boolean (value, gss->show.stripes_col); break;
138 	case PROP_SHOW_STRIPES_ROW : g_value_set_boolean (value, gss->show.stripes_row); break;
139 	case PROP_SHOW_LAST_COL    : g_value_set_boolean (value, gss->show.last_col); break;
140 	case PROP_SHOW_LAST_ROW    : g_value_set_boolean (value, gss->show.last_row); break;
141 
142 	case PROP_LAYOUT : g_value_set_enum (value, gss->layout); break;
143 	default:
144 		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec);
145 	}
146 }
147 
148 static void
gnm_sheet_slicer_class_init(GnmSheetSlicerClass * klass)149 gnm_sheet_slicer_class_init (GnmSheetSlicerClass *klass)
150 {
151 	GObjectClass *gobject_class = (GObjectClass *)klass;
152 	gobject_class->set_property	= gnm_sheet_slicer_set_property;
153 	gobject_class->get_property	= gnm_sheet_slicer_get_property;
154 	gobject_class->finalize		= gnm_sheet_slicer_finalize;
155 
156 	g_object_class_install_property (gobject_class, PROP_SHEET,
157 		 g_param_spec_object ("sheet", NULL, NULL, GNM_SHEET_TYPE,
158 			GSF_PARAM_STATIC | G_PARAM_READWRITE));
159 	g_object_class_install_property (gobject_class, PROP_RANGE,
160 		 g_param_spec_boxed ("range", NULL, NULL, gnm_range_get_type (),
161 			GSF_PARAM_STATIC | G_PARAM_READWRITE));
162 	g_object_class_install_property (gobject_class, PROP_FIRST_HEADER_ROW,
163 		 g_param_spec_uint ("first-header-row", NULL, NULL, 0, GNM_MAX_ROWS, 0,
164 			GSF_PARAM_STATIC | G_PARAM_READWRITE));
165 	g_object_class_install_property (gobject_class, PROP_FIRST_DATA_COL,
166 		 g_param_spec_uint ("first-data-col", NULL, NULL, 0, GNM_MAX_COLS, 0,
167 			GSF_PARAM_STATIC | G_PARAM_READWRITE));
168 	g_object_class_install_property (gobject_class, PROP_FIRST_DATA_ROW,
169 		 g_param_spec_uint ("first-data-row", NULL, NULL, 0, GNM_MAX_ROWS, 0,
170 			GSF_PARAM_STATIC | G_PARAM_READWRITE));
171 
172 	g_object_class_install_property (gobject_class, PROP_SHOW_HEADERS_COL,
173 		g_param_spec_boolean ("show-headers-col", NULL, NULL, TRUE,
174 			GSF_PARAM_STATIC | G_PARAM_READWRITE));
175 	g_object_class_install_property (gobject_class, PROP_SHOW_HEADERS_ROW,
176 		g_param_spec_boolean ("show-headers-row", NULL, NULL, TRUE,
177 			GSF_PARAM_STATIC | G_PARAM_READWRITE));
178 	g_object_class_install_property (gobject_class, PROP_SHOW_STRIPES_COL,
179 		g_param_spec_boolean ("show-stripes-col", NULL, NULL, TRUE,
180 			GSF_PARAM_STATIC | G_PARAM_READWRITE));
181 	g_object_class_install_property (gobject_class, PROP_SHOW_STRIPES_ROW,
182 		g_param_spec_boolean ("show-stripes-row", NULL, NULL, TRUE,
183 			GSF_PARAM_STATIC | G_PARAM_READWRITE));
184 	g_object_class_install_property (gobject_class, PROP_SHOW_LAST_COL,
185 		g_param_spec_boolean ("show-last-col", NULL, NULL, TRUE,
186 			GSF_PARAM_STATIC | G_PARAM_READWRITE));
187 	g_object_class_install_property (gobject_class, PROP_SHOW_LAST_ROW,
188 		g_param_spec_boolean ("show-last-row", NULL, NULL, TRUE,
189 			GSF_PARAM_STATIC | G_PARAM_READWRITE));
190 
191 	g_object_class_install_property (gobject_class, PROP_LAYOUT,
192 		 g_param_spec_enum ("layout", NULL, NULL, gnm_sheet_slicer_layout_get_type (), GSS_LAYOUT_XL_OUTLINE,
193 			GSF_PARAM_STATIC | G_PARAM_READWRITE));
194 	parent_klass = g_type_class_peek_parent (klass);
195 }
196 
GSF_CLASS(GnmSheetSlicer,gnm_sheet_slicer,gnm_sheet_slicer_class_init,gnm_sheet_slicer_init,GO_DATA_SLICER_TYPE)197 GSF_CLASS (GnmSheetSlicer, gnm_sheet_slicer,
198 	   gnm_sheet_slicer_class_init, gnm_sheet_slicer_init,
199 	   GO_DATA_SLICER_TYPE)
200 
201 void
202 gnm_sheet_slicer_set_sheet (GnmSheetSlicer *gss, Sheet *sheet)
203 {
204 	g_return_if_fail (IS_SHEET (sheet));
205 	g_return_if_fail (GNM_IS_SHEET_SLICER (gss));
206 	g_return_if_fail (NULL == gss->sheet);
207 
208 	g_object_ref (gss);
209 	gss->sheet = sheet;
210 	sheet->slicers = g_slist_prepend (sheet->slicers, gss);
211 }
212 
213 void
gnm_sheet_slicer_clear_sheet(GnmSheetSlicer * gss)214 gnm_sheet_slicer_clear_sheet (GnmSheetSlicer *gss)
215 {
216 	g_return_if_fail (GNM_IS_SHEET_SLICER (gss));
217 	g_return_if_fail (NULL != gss->sheet);
218 
219 	gss->sheet->slicers = g_slist_remove (gss->sheet->slicers, gss);
220 	gss->sheet = NULL;
221 	g_object_unref (gss);
222 }
223 
224 GnmRange const	*
gnm_sheet_slicer_get_range(GnmSheetSlicer const * gss)225 gnm_sheet_slicer_get_range (GnmSheetSlicer const *gss)
226 {
227 	g_return_val_if_fail (GNM_IS_SHEET_SLICER (gss), NULL);
228 	return &gss->range;
229 }
230 
231 void
gnm_sheet_slicer_set_range(GnmSheetSlicer * gss,GnmRange const * r)232 gnm_sheet_slicer_set_range (GnmSheetSlicer *gss, GnmRange const *r)
233 {
234 	g_return_if_fail (GNM_IS_SHEET_SLICER (gss));
235 	gss->range = *r;
236 }
237 
238 /**
239  * gnm_sheet_slicer_overlaps_range:
240  * @gss: #GnmSheetSlicer
241  * @r: #GnmRange
242  *
243  * Returns: %TRUE if @gss overlaps @r.
244  **/
245 gboolean
gnm_sheet_slicer_overlaps_range(GnmSheetSlicer const * gss,GnmRange const * r)246 gnm_sheet_slicer_overlaps_range (GnmSheetSlicer const *gss, GnmRange const *r)
247 {
248 	g_return_val_if_fail (GNM_IS_SHEET_SLICER (gss), FALSE);
249 	return range_overlap (&gss->range, r);
250 }
251 
252 /**
253  * gnm_sheet_slicer_field_header_at_pos:
254  * @gss: #GnmSheetSlicer const
255  * @pos: #GnmCellPos const
256  *
257  * Checks to see if @pos (in absolute position, not relative to @gss' corner)
258  * corresponds to a field header.  [Does not add a reference]
259  *
260  * Returns a #GODataSlicerField or %NULL.
261  **/
262 GODataSlicerField *
gnm_sheet_slicer_field_header_at_pos(GnmSheetSlicer const * gss,GnmCellPos const * pos)263 gnm_sheet_slicer_field_header_at_pos (GnmSheetSlicer const *gss,
264 				      GnmCellPos const *pos)
265 {
266 	int res = -1;
267 	unsigned int c, r;
268 
269 	g_return_val_if_fail (GNM_IS_SHEET_SLICER (gss), NULL);
270 
271 	/* 0) TODO page fields */
272 	if (pos->col < gss->range.start.col || pos->row < gss->range.start.row)
273 		return NULL;
274 
275 	c = pos->col - gss->range.start.col;
276 	r = pos->row - gss->range.start.row;
277 
278 	/* TODO other layouts */
279 
280 	/* col headers along the top starting at first_data_col */
281 	if (r == 0 &&
282 	    c >= gss->first_data_col) {
283 		c -= gss->first_data_col;
284 		if (c < gss->base.fields[GDS_FIELD_TYPE_COL]->len)
285 			res = g_array_index (gss->base.fields[GDS_FIELD_TYPE_COL], int, c);
286 
287 
288 	/* row headers just about data starting at 0th col */
289 	} else if (r >= (gss->first_data_row - 1) &&	/* -1 for the headers */
290 		   c < gss->first_data_col) {
291 		if (c < gss->base.fields[GDS_FIELD_TYPE_ROW]->len)
292 			res = g_array_index (gss->base.fields[GDS_FIELD_TYPE_ROW], int, c);
293 	}
294 
295 	return (res >= 0) ? go_data_slicer_get_field (&gss->base, res) : NULL;
296 }
297 
298 /************************************************************/
299 
300 /**
301  * gnm_sheet_slicers_at_pos:
302  * @sheet: #Sheet
303  * @pos: #GnmCellPos
304  *
305  * Returns: (transfer none): %NULL or the #GnmSheetSlicer in @sheet that overlaps with @pos.
306  **/
307 GnmSheetSlicer *
gnm_sheet_slicers_at_pos(Sheet const * sheet,GnmCellPos const * pos)308 gnm_sheet_slicers_at_pos (Sheet const *sheet, GnmCellPos const *pos)
309 {
310 	GSList *ptr;
311 	GnmRange r;
312 
313 	g_return_val_if_fail (IS_SHEET (sheet), NULL);
314 	g_return_val_if_fail (NULL != pos, NULL);
315 
316 	range_init_cellpos (&r, pos);
317 	for (ptr = sheet->slicers; ptr != NULL ; ptr = ptr->next)
318 		if (gnm_sheet_slicer_overlaps_range (ptr->data, &r))
319 			return ptr->data;
320 
321 	return NULL;
322 }
323 
324 #if 0
325 static void
326 gss_append_field_indicies (GnmSheetSlicer const *gss, GODataSlicerFieldType type,
327 			   GArray *field_order)
328 {
329 	GArray *tmp = gss->base.fields [type];
330 	unsigned int i, n = tmp->len;
331 	for (i = 0 ; i < n; i++)
332 		g_array_append_val (field_order, g_array_index (tmp, int, i));
333 }
334 
335 static void
336 gnm_sheet_slicer_test_sort (GnmSheetSlicer *gss)
337 {
338 	/* quick test to sort the cache based on the row/col */
339 	GArray *permutation, *field_order;
340 	unsigned int i, n;
341 
342 	field_order = g_array_sized_new (FALSE, FALSE, sizeof (unsigned int), gss->base.all_fields->len);
343 	gss_append_field_indicies (gss, GDS_FIELD_TYPE_ROW, field_order);
344 	gss_append_field_indicies (gss, GDS_FIELD_TYPE_COL, field_order);
345 
346 	n = go_data_cache_num_items (gss->base.cache);
347 	permutation = g_array_sized_new (FALSE, FALSE, sizeof (int), n);
348 	for (i = 0 ; i < n ; i++)
349 		g_array_append_val (permutation, i);
350 	go_data_cache_permute (gss->base.cache, field_order, permutation);
351 	go_data_cache_dump (gss->base.cache, field_order, permutation);
352 
353 	g_array_free (field_order, TRUE);
354 	g_array_free (permutation, TRUE);
355 }
356 #endif
357 
358 /**
359  * gnm_sheet_slicer_regenerate:
360  * @gss: #GnmSheetSlicer
361  *
362  * Do some work!
363  * See what we need to do then think about when portions belong in the GODataSlicer base.
364  *
365  **/
366 void
gnm_sheet_slicer_regenerate(GnmSheetSlicer * gss)367 gnm_sheet_slicer_regenerate (GnmSheetSlicer *gss)
368 {
369 #if 0
370 	GArray *permutation, *rows;
371 	unsigned int i, n;
372 
373 	g_return_if_fail (GNM_IS_SHEET_SLICER (gss));
374 	g_return_if_fail (IS_SHEET (gss->sheet));
375 	g_return_if_fail (NULL != gss->base.cache);
376 
377 	field_order = g_array_sized_new (FALSE, FALSE, sizeof (unsigned int), gss->base.all_fields->len);
378 	gss_append_field_indicies (gss, GDS_FIELD_TYPE_ROW, field_order);
379 	gss_append_field_indicies (gss, GDS_FIELD_TYPE_COL, field_order);
380 
381 	n = go_data_cache_num_items (gss->base.cache);
382 #endif
383 }
384 
385 GnmSheetSlicerLayout
gnm_sheet_slicer_get_layout(GnmSheetSlicer const * gss)386 gnm_sheet_slicer_get_layout (GnmSheetSlicer const *gss)
387 {
388 	g_return_val_if_fail (GNM_IS_SHEET_SLICER (gss), GSS_LAYOUT_XL_OUTLINE);
389 	return gss->layout;
390 }
391 
392 void
gnm_sheet_slicer_set_layout(GnmSheetSlicer * gss,GnmSheetSlicerLayout l)393 gnm_sheet_slicer_set_layout (GnmSheetSlicer *gss, GnmSheetSlicerLayout l)
394 {
395 	g_return_if_fail (GNM_IS_SHEET_SLICER (gss));
396 	gss->layout = l;
397 }
398 
399 GType
gnm_sheet_slicer_layout_get_type(void)400 gnm_sheet_slicer_layout_get_type (void)
401 {
402 	static GType etype = 0;
403 	if (etype == 0) {
404 		static GEnumValue const values[] = {
405 			{ GSS_LAYOUT_XL_OUTLINE, "GSS_LAYOUT_XL_OUTLINE", "xl-outline" },
406 			{ GSS_LAYOUT_XL_COMPACT, "GSS_LAYOUT_XL_COMPACT", "xl-compact" },
407 			{ GSS_LAYOUT_XL_TABULAR, "GSS_LAYOUT_XL_TABULAR", "xl-tabular" },
408 			{ 0, NULL, NULL }
409 		};
410 		etype = g_enum_register_static ("GnmSheetSlicerLayout", values);
411 	}
412 	return etype;
413 }
414