output view: make items selectable and use system colours
[pspp] / src / output / spv / spv-select.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2018 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include "spv-select.h"
20
21 #include <string.h>
22
23 #include "libpspp/assertion.h"
24 #include "libpspp/bit-vector.h"
25 #include "output/spv/spv.h"
26
27 #include "gl/c-ctype.h"
28 #include "gl/xalloc.h"
29
30 static struct spv_item *
31 find_command_item (struct spv_item *item)
32 {
33   /* A command item itself does not have a command item. */
34   if (!item->parent || !item->parent->parent)
35     return NULL;
36
37   do
38     {
39       item = item->parent;
40     }
41   while (item->parent && item->parent->parent);
42   return item;
43 }
44
45 static bool
46 string_matches (const char *pattern, const char *s)
47 {
48   /* XXX This should be a Unicode case insensitive comparison. */
49   while (c_tolower (*pattern) == c_tolower (*s))
50     {
51       if (*pattern == '\0')
52         return true;
53
54       pattern++;
55       s++;
56     }
57
58   return pattern[0] == '*' && pattern[1] == '\0';
59 }
60
61 static int
62 string_array_matches (const char *name, const struct string_array *array)
63 {
64   if (!array->n)
65     return -1;
66   else if (!name)
67     return false;
68
69   for (size_t i = 0; i < array->n; i++)
70     if (string_matches (array->strings[i], name))
71       return true;
72
73   return false;
74 }
75
76 static bool
77 match (const char *name,
78        const struct string_array *white,
79        const struct string_array *black)
80 {
81   return (string_array_matches (name, white) != false
82           && string_array_matches (name, black) != true);
83 }
84
85 static int
86 match_instance (const int *instances, size_t n_instances,
87                 int instance_within_command)
88 {
89   int retval = false;
90   for (size_t i = 0; i < n_instances; i++)
91     {
92       if (instances[i] == instance_within_command)
93         return true;
94       else if (instances[i] == -1)
95         retval = -1;
96     }
97   return retval;
98 }
99
100 static void
101 select_matches (const struct spv_reader *spv, const struct spv_criteria *c,
102                 unsigned long int *include)
103 {
104   struct spv_item *item;
105   struct spv_item *command_item = NULL;
106   int instance_within_command = 0;
107   int last_instance = -1;
108   ssize_t index = -1;
109   SPV_ITEM_FOR_EACH_SKIP_ROOT (item, spv_get_root (spv))
110     {
111       index++;
112
113       struct spv_item *new_command_item = find_command_item (item);
114       if (new_command_item != command_item)
115         {
116           if (last_instance >= 0)
117             {
118               bitvector_set1 (include, last_instance);
119               last_instance = -1;
120             }
121
122           command_item = new_command_item;
123           instance_within_command = 0;
124         }
125
126       if (!((1u << spv_item_get_class (item)) & c->classes))
127         continue;
128
129       if (!c->include_hidden && !spv_item_is_visible (item))
130         continue;
131
132       if (c->error)
133         {
134           spv_item_load (item);
135           if (!item->error)
136             continue;
137         }
138
139       if (!match (spv_item_get_command_id (item),
140                   &c->include.commands, &c->exclude.commands))
141         continue;
142
143       if (!match (spv_item_get_subtype (item),
144                   &c->include.subtypes, &c->exclude.subtypes))
145         continue;
146
147       if (!match (spv_item_get_label (item),
148                   &c->include.labels, &c->exclude.labels))
149         continue;
150
151       if (c->members.n
152           && !((item->xml_member
153                 && string_array_matches (item->xml_member, &c->members)) ||
154                (item->bin_member
155                 && string_array_matches (item->bin_member, &c->members))))
156         continue;
157
158       if (c->n_instances)
159         {
160           if (!command_item)
161             continue;
162           instance_within_command++;
163
164           int include_instance = match_instance (c->instances, c->n_instances,
165                                                  instance_within_command);
166           if (!include_instance)
167             continue;
168           else if (include_instance < 0)
169             {
170               last_instance = index;
171               continue;
172             }
173         }
174
175       bitvector_set1 (include, index);
176     }
177
178   if (last_instance >= 0)
179     bitvector_set1 (include, last_instance);
180 }
181
182 void
183 spv_select (const struct spv_reader *spv,
184             const struct spv_criteria c[], size_t nc,
185             struct spv_item ***itemsp, size_t *n_itemsp)
186 {
187   struct spv_item *item;
188
189   struct spv_criteria default_criteria = SPV_CRITERIA_INITIALIZER;
190   if (!nc)
191     {
192       nc = 1;
193       c = &default_criteria;
194     }
195
196   /* Count items. */
197   size_t max_items = 0;
198   SPV_ITEM_FOR_EACH_SKIP_ROOT (item, spv_get_root (spv))
199     max_items++;
200
201   /* Allocate bitmap for items then fill it in with selected items. */
202   unsigned long int *include = bitvector_allocate (max_items);
203   for (size_t i = 0; i < nc; i++)
204     select_matches (spv, &c[i], include);
205
206   /* Copy selected items into output array. */
207   size_t n_items = 0;
208   struct spv_item **items = xnmalloc (bitvector_count (include, max_items),
209                                       sizeof *items);
210   size_t i = 0;
211   SPV_ITEM_FOR_EACH_SKIP_ROOT (item, spv_get_root (spv))
212     if (bitvector_is_set (include, i++))
213       items[n_items++] = item;
214   *itemsp = items;
215   *n_itemsp = n_items;
216
217   /* Free memory. */
218   free (include);
219 }