1.12.42
[gnumeric.git] / src / widgets / gnm-filter-combo-view.c
blob6a4f5909b343cca7dd095dcb6ee1e5b122732e58
2 /*
3 * gnm-filter-combo.c: the autofilter combo box for the goffice canvas
5 * Copyright (C) 2006 Jody Goldberg (jody@gnome.org)
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation; either version 2 of the
10 * License, or (at your option) version 3.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
20 * USA
23 #include <gnumeric-config.h>
24 #include <widgets/gnm-filter-combo-view.h>
25 #include <widgets/gnm-cell-combo-view-impl.h>
27 #include <gnumeric.h>
28 #include <sheet-filter.h>
29 #include <sheet-filter-combo.h>
30 #include <gnm-format.h>
31 #include <value.h>
32 #include <cell.h>
33 #include <commands.h>
34 #include <sheet.h>
35 #include <sheet-object-impl.h>
36 #include <wbc-gtk.h>
37 #include <workbook.h>
38 #include <style-color.h>
39 #include <sheet-control-gui.h>
40 #include <dialogs/dialogs.h>
41 #include <wbc-gtk-impl.h>
42 #include <undo.h>
44 #include <goffice/goffice.h>
45 #include <gsf/gsf-impl-utils.h>
46 #include <glib/gi18n-lib.h>
47 #include <string.h>
49 static gboolean
50 fcombo_activate (SheetObject *so, GtkTreeView *list, WBCGtk *wbcg,
51 G_GNUC_UNUSED gboolean button)
53 GnmFilterCombo *fcombo = GNM_FILTER_COMBO (so);
54 GtkTreeIter iter;
56 if (gtk_tree_selection_get_selected (gtk_tree_view_get_selection (list), NULL, &iter)) {
57 GnmFilterCondition *cond = NULL;
58 gboolean set_condition = TRUE;
59 GnmValue *v;
60 int field_num, type;
62 gtk_tree_model_get (gtk_tree_view_get_model (list), &iter,
63 2, &type, 3, &v,
64 -1);
66 field_num = gnm_filter_combo_index (fcombo);
67 switch (type) {
68 case 0:
69 cond = gnm_filter_condition_new_single (
70 GNM_FILTER_OP_EQUAL, v);
71 break;
72 case 1: /* unfilter */
73 cond = NULL;
74 break;
75 case 2: /* Custom */
76 set_condition = FALSE;
77 dialog_auto_filter (wbcg, fcombo->filter, field_num,
78 TRUE, fcombo->cond);
79 break;
80 case 3:
81 cond = gnm_filter_condition_new_single (
82 GNM_FILTER_OP_BLANKS, NULL);
83 break;
84 case 4:
85 cond = gnm_filter_condition_new_single (
86 GNM_FILTER_OP_NON_BLANKS, NULL);
87 break;
88 case 10: /* Top 10 */
89 set_condition = FALSE;
90 dialog_auto_filter (wbcg, fcombo->filter, field_num,
91 FALSE, fcombo->cond);
92 break;
93 default:
94 set_condition = FALSE;
95 g_warning ("Unknown type %d", type);
98 if (set_condition)
99 cmd_autofilter_set_condition
100 (GNM_WBC (wbcg),
101 fcombo->filter, field_num, cond);
103 return TRUE;
106 typedef struct {
107 gboolean has_blank;
108 GHashTable *hash;
109 GODateConventions const *date_conv;
110 Sheet *src_sheet;
111 } UniqueCollection;
113 static GnmValue *
114 cb_collect_content (GnmCellIter const *iter, UniqueCollection *uc)
116 GnmCell const *cell = (iter->pp.sheet == uc->src_sheet) ? iter->cell
117 : sheet_cell_get (uc->src_sheet,
118 iter->pp.eval.col, iter->pp.eval.row);
120 if (gnm_cell_is_blank (cell))
121 uc->has_blank = TRUE;
122 else {
123 GOFormat const *fmt = gnm_cell_get_format (cell);
124 GnmValue const *v = cell->value;
125 g_hash_table_replace (uc->hash,
126 value_dup (v),
127 format_value (fmt, v, -1, uc->date_conv));
130 return NULL;
133 static void
134 cb_hash_domain (GnmValue *key, gpointer value, gpointer accum)
136 g_ptr_array_add (accum, key);
139 /* Distinguish between the same value with different formats */
140 static gboolean
141 formatted_value_equal (GnmValue const *a, GnmValue const *b)
143 return value_equal (a, b) && (VALUE_FMT(a) == VALUE_FMT(b));
146 static GtkWidget *
147 fcombo_create_list (SheetObject *so,
148 GtkTreePath **clip, GtkTreePath **select, gboolean *make_buttons)
150 GnmFilterCombo *fcombo = GNM_FILTER_COMBO (so);
151 GnmFilter const *filter = fcombo->filter;
152 GnmRange r = filter->r;
153 Sheet *filtered_sheet;
154 UniqueCollection uc;
155 GtkTreeIter iter;
156 GtkListStore *model;
157 GtkWidget *list;
158 GPtrArray *sorted = g_ptr_array_new ();
159 unsigned i, field_num = gnm_filter_combo_index (fcombo);
160 gboolean is_custom = FALSE;
161 GnmValue const *v;
162 GnmValue const *cur_val = NULL;
164 model = gtk_list_store_new (4,
165 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT, gnm_value_get_type ());
167 gtk_list_store_append (model, &iter);
168 gtk_list_store_set (model, &iter, 0, _("(All)"), 1, NULL, 2, 1, -1);
169 if (fcombo->cond == NULL || fcombo->cond->op[0] == GNM_FILTER_UNUSED)
170 *select = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
172 gtk_list_store_append (model, &iter);
173 gtk_list_store_set (model, &iter, 0, _("(Top 10...)"), 1, NULL, 2, 10,-1);
174 if (fcombo->cond != NULL &&
175 (GNM_FILTER_OP_TYPE_MASK & fcombo->cond->op[0]) == GNM_FILTER_OP_TOP_N)
176 *select = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
178 /* default to this we can easily revamp later */
179 gtk_list_store_append (model, &iter);
180 gtk_list_store_set (model, &iter, 0, _("(Custom...)"), 1, NULL, 2, 2, -1);
181 if (*select == NULL) {
182 is_custom = TRUE;
183 *select = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
186 r.start.row++;
187 /* r.end.row = XL actually extend to the first non-empty element in the list */
188 r.end.col = r.start.col += field_num;
189 uc.has_blank = FALSE;
190 uc.hash = g_hash_table_new_full ((GHashFunc)value_hash, (GEqualFunc)formatted_value_equal,
191 (GDestroyNotify)value_release, (GDestroyNotify)g_free);
192 uc.src_sheet = filter->sheet;
193 uc.date_conv = sheet_date_conv (uc.src_sheet);
195 /* We do not want to show items that are filtered by _other_ fields.
196 * The cleanest way to do that is to create a temporary sheet, apply
197 * all of the other conditions to it and use that as the source of visibility. */
198 if (filter->fields->len > 1) {
199 Workbook *wb = uc.src_sheet->workbook;
200 char *name = workbook_sheet_get_free_name (wb, "DummyFilterPopulate", FALSE, FALSE);
201 filtered_sheet = sheet_new (wb, name,
202 gnm_sheet_get_max_cols (uc.src_sheet),
203 gnm_sheet_get_max_rows (uc.src_sheet));
204 g_free (name);
205 for (i = 0 ; i < filter->fields->len ; i++)
206 if (i != field_num)
207 gnm_filter_combo_apply (g_ptr_array_index (filter->fields, i),
208 filtered_sheet);
209 sheet_foreach_cell_in_range (filtered_sheet,
210 CELL_ITER_IGNORE_HIDDEN,
212 (CellIterFunc)&cb_collect_content, &uc);
213 g_object_unref (filtered_sheet);
214 } else
215 sheet_foreach_cell_in_range (filter->sheet, CELL_ITER_ALL,
217 (CellIterFunc)&cb_collect_content, &uc);
219 g_hash_table_foreach (uc.hash, (GHFunc)cb_hash_domain, sorted);
220 g_ptr_array_sort (sorted, value_cmp);
222 if (fcombo->cond != NULL &&
223 fcombo->cond->op[0] == GNM_FILTER_OP_EQUAL &&
224 fcombo->cond->op[1] == GNM_FILTER_UNUSED) {
225 cur_val = fcombo->cond->value[0];
228 for (i = 0; i < sorted->len ; i++) {
229 char *label = NULL;
230 unsigned const max = 50;
231 char const *str = g_hash_table_lookup (uc.hash,
232 (v = g_ptr_array_index (sorted, i)));
233 gsize len = g_utf8_strlen (str, -1);
235 if (len > max + 3) {
236 label = g_strdup (str);
237 strcpy (g_utf8_offset_to_pointer (label, max), "...");
240 gtk_list_store_append (model, &iter);
241 gtk_list_store_set (model, &iter,
242 0, label ? label : str, /* Menu text */
243 1, str, /* Actual string selected on. */
244 2, 0,
245 3, v,
246 -1);
247 g_free (label);
248 if (i == 10)
249 *clip = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
250 if (cur_val != NULL && v != NULL && value_equal (cur_val, v)) {
251 gtk_tree_path_free (*select);
252 *select = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
256 if (uc.has_blank) {
257 gtk_list_store_append (model, &iter);
258 gtk_list_store_set (model, &iter, 0, _("(Blanks...)"), 1, NULL, 2, 3, -1);
259 if (fcombo->cond != NULL &&
260 fcombo->cond->op[0] == GNM_FILTER_OP_BLANKS)
261 *select = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
263 gtk_list_store_append (model, &iter);
264 gtk_list_store_set (model, &iter, 0, _("(Non Blanks...)"), 1, NULL, 2, 4, -1);
265 if (fcombo->cond != NULL &&
266 fcombo->cond->op[0] == GNM_FILTER_OP_NON_BLANKS)
267 *select = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
268 } else if (is_custom && fcombo->cond != NULL &&
269 (GNM_FILTER_OP_TYPE_MASK & fcombo->cond->op[0]) == GNM_FILTER_OP_BLANKS) {
270 gtk_tree_path_free (*select);
271 *select = NULL;
274 g_hash_table_destroy (uc.hash);
275 g_ptr_array_free (sorted, TRUE);
277 list = gtk_tree_view_new_with_model (GTK_TREE_MODEL (model));
278 g_object_unref (model);
279 gtk_tree_view_append_column (GTK_TREE_VIEW (list),
280 gtk_tree_view_column_new_with_attributes ("ID",
281 gtk_cell_renderer_text_new (), "text", 0,
282 NULL));
283 return list;
286 static void
287 fcombo_arrow_format (GnmFilterCombo *fcombo, GtkWidget *arrow)
289 if (gtk_widget_get_parent (arrow)) {
290 char *desc = NULL;
291 if (NULL != fcombo->cond) {
293 if (desc) {
294 gtk_widget_set_tooltip_text (gtk_widget_get_parent (arrow), desc);
295 g_free (desc);
299 gtk_arrow_set (GTK_ARROW (arrow),
300 fcombo->cond != NULL ? GTK_ARROW_RIGHT : GTK_ARROW_DOWN,
301 GTK_SHADOW_IN);
302 if (fcombo->cond)
303 gtk_widget_set_state_flags (arrow, GTK_STATE_FLAG_ACTIVE, FALSE);
304 else
305 gtk_widget_unset_state_flags (arrow, GTK_STATE_FLAG_ACTIVE);
308 static GtkWidget *
309 fcombo_create_arrow (SheetObject *so)
311 GnmFilterCombo *fcombo = GNM_FILTER_COMBO (so);
312 GtkWidget *arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_IN);
313 gtk_style_context_add_class (gtk_widget_get_style_context (arrow),
314 "auto-filter");
315 fcombo_arrow_format (fcombo, arrow);
316 g_signal_connect_object (G_OBJECT (so),
317 "cond-changed",
318 G_CALLBACK (fcombo_arrow_format), arrow, 0);
319 return arrow;
322 /*******************************************************************************/
324 /* Somewhat magic.
325 * We do not honour all of the anchor flags. All that is used is the far corner. */
326 static void
327 filter_view_set_bounds (SheetObjectView *sov, double const *coords, gboolean visible)
329 GocGroup *view = GOC_GROUP (sov);
331 if (visible) {
332 double scale = goc_canvas_get_pixels_per_unit (GOC_ITEM (view)->canvas);
333 double h = (coords[3] - coords[1]) + 1.;
334 if (h > 20.) /* clip vertically */
335 h = 20.;
336 h /= scale;
337 goc_item_set (GOC_ITEM (view->children->data),
338 /* put it inside the cell */
339 "x", ((coords[2] >= 0.) ? (coords[2] / scale - h + 1) : coords[0] / scale),
340 "y", coords [3] / scale - h + 1.,
341 "width", h, /* force a square, use h for width too */
342 "height", h,
343 NULL);
345 goc_item_show (GOC_ITEM (view));
346 } else
347 goc_item_hide (GOC_ITEM (view));
350 static void
351 gnm_filter_view_class_init (GnmCComboViewClass *ccombo_class)
353 SheetObjectViewClass *sov_class = (SheetObjectViewClass *) ccombo_class;
354 ccombo_class->create_list = fcombo_create_list;
355 ccombo_class->create_arrow = fcombo_create_arrow;
356 ccombo_class->activate = fcombo_activate;
357 sov_class->set_bounds = filter_view_set_bounds;
360 /****************************************************************************/
362 typedef GnmCComboView GnmFilterComboView;
363 typedef GnmCComboViewClass GnmFilterComboViewClass;
364 GSF_CLASS (GnmFilterComboView, gnm_filter_combo_view,
365 gnm_filter_view_class_init, NULL,
366 GNM_CCOMBO_VIEW_TYPE)