Warnings: function type cast for g_list_foreach
[pspp] / src / ui / gui / psppire-acr.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2007, 2012 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
18 /*
19   This widget is a GtkBox which looks roughly like:
20
21   +-----------------------------+
22   |+------------+  +----------+ |
23   ||   Add      |  |          | |
24   |+------------+  |          | |
25   |                |          | |
26   |+------------+  |          | |
27   ||   Edit     |  |          | |
28   |+------------+  |          | |
29   |                |          | |
30   |+------------+  |          | |
31   ||  Remove    |  |          | |
32   |+------------+  +----------+ |
33   +-----------------------------+
34
35 */
36
37 #include <config.h>
38 #include <gtk/gtk.h>
39
40 #include "psppire-acr.h"
41 #include "helper.h"
42
43 #include "gettext.h"
44 #define _(msgid) gettext (msgid)
45 #define N_(msgid) msgid
46
47 G_DEFINE_TYPE (PsppireAcr, psppire_acr, GTK_TYPE_BOX);
48
49 static void
50 psppire_acr_dispose (GObject *obj)
51 {
52   PsppireAcr *acr = PSPPIRE_ACR (obj);
53
54   if (acr->dispose_has_run)
55     return;
56   acr->dispose_has_run = TRUE;
57
58   psppire_acr_set_model (acr, NULL);
59
60   G_OBJECT_CLASS (psppire_acr_parent_class)->dispose (obj);
61 }
62
63 static void
64 psppire_acr_class_init (PsppireAcrClass *class)
65 {
66   G_OBJECT_CLASS (class)->dispose = psppire_acr_dispose;
67 }
68
69 static gboolean row_is_selected (const PsppireAcr *acr);
70
71
72 static gboolean
73 value_from_entry (gint col, GValue *val, gpointer data)
74 {
75   GtkEntry *entry = data;
76   const gchar *text = gtk_entry_get_text (entry);
77   gdouble x = g_strtod (text, 0);
78
79   g_value_init (val, G_TYPE_DOUBLE);
80   g_value_set_double (val, x);
81
82   return TRUE;
83 }
84
85
86 /* Returns true, if there's text in the entry */
87 static gboolean
88 entry_not_empty (gpointer data)
89 {
90   GtkEntry *entry = data;
91
92   const char *text = gtk_entry_get_text (entry);
93
94   return !g_str_equal (text, "");
95 }
96
97
98 static void
99 clear_entry (gpointer data)
100 {
101   GtkEntry *entry = data;
102   gtk_entry_set_text (entry, "");
103 }
104
105
106 static void
107 on_entry_change (GtkEntry *entry, PsppireAcr *acr)
108 {
109   gtk_widget_set_sensitive (acr->add_button, acr->enabled (entry));
110
111   gtk_widget_set_sensitive (acr->change_button, acr->enabled (entry)
112                             && row_is_selected (acr));
113 }
114
115 void
116 psppire_acr_set_entry  (PsppireAcr *acr, GtkEntry *entry)
117 {
118   acr->get_value = value_from_entry;
119   acr->get_value_data = entry;
120   acr->enabled = entry_not_empty;
121   acr->enabled_data = entry;
122   acr->update = clear_entry;
123   acr->update_data = entry;
124
125   g_signal_connect (entry, "changed", G_CALLBACK (on_entry_change), acr);
126 }
127
128
129 /* Callback for when the Add button is clicked.
130    It appends an item to the list. */
131 static void
132 on_add_button_clicked (PsppireAcr *acr)
133 {
134   gint i;
135   GtkTreeIter iter;
136   gtk_list_store_append (acr->list_store, &iter);
137
138   for (i = 0 ;
139        i < gtk_tree_model_get_n_columns (GTK_TREE_MODEL (acr->list_store));
140        ++i)
141     {
142       static GValue value;
143       if (! acr->get_value (i, &value, acr->get_value_data))
144         continue;
145
146       gtk_list_store_set_value (acr->list_store, &iter,
147                                 i, &value);
148       g_value_unset (&value);
149     }
150
151   if (acr->update) acr->update (acr->update_data);
152 }
153
154
155 /* Callback for when the Changed button is clicked.
156    It replaces the currently selected entry. */
157 static void
158 on_change_button_clicked (PsppireAcr *acr)
159 {
160   gint i;
161   GtkTreeModel *model = GTK_TREE_MODEL (acr->list_store);
162
163   GList *l=
164     gtk_tree_selection_get_selected_rows (acr->selection,
165                                           &model);
166
167   GtkTreePath *path = l->data;
168
169   GtkTreeIter iter;
170
171   gtk_tree_model_get_iter (model, &iter, path);
172
173   for (i = 0 ;
174        i < gtk_tree_model_get_n_columns (GTK_TREE_MODEL (acr->list_store));
175        ++i)
176     {
177       static GValue value;
178       if (! acr->get_value (i, &value, acr->get_value_data))
179         continue;
180
181       gtk_list_store_set_value (acr->list_store, &iter,
182                                 i, &value);
183       g_value_unset (&value);
184     }
185
186   g_list_foreach (l, (GFunc) (void (*)(void)) gtk_tree_path_free, NULL);
187   g_list_free (l);
188
189   if (acr->update) acr->update (acr->update_data);
190 }
191
192
193 /* Callback for when the remove button is clicked.
194    It deletes the currently selected entry. */
195 static void
196 on_remove_button_clicked (PsppireAcr *acr)
197 {
198   GtkTreeModel *model = GTK_TREE_MODEL (acr->list_store);
199
200   GList *l=
201     gtk_tree_selection_get_selected_rows (acr->selection,
202                                           &model);
203
204   GtkTreePath *path = l->data;
205
206   GtkTreeIter iter;
207
208   gtk_tree_model_get_iter (model, &iter, path);
209
210   gtk_list_store_remove (acr->list_store, &iter);
211
212   g_list_foreach (l, (GFunc) (void (*)(void)) gtk_tree_path_free, NULL);
213   g_list_free (l);
214 }
215
216 /* Returns true if there is a row currently selected.
217    False otherwise. */
218 static gboolean
219 row_is_selected (const PsppireAcr *acr)
220 {
221   gboolean result;
222   GtkTreeModel *model = GTK_TREE_MODEL (acr->list_store);
223   GList *l = gtk_tree_selection_get_selected_rows (acr->selection,
224                                                    &model);
225
226   result = (l != NULL);
227
228   g_list_foreach (l, (GFunc) (void (*)(void)) gtk_tree_path_free, NULL);
229   g_list_free (l);
230
231   return result;
232 }
233
234
235 /* Callback which occurs when an item in the treeview
236    is selected */
237 static void
238 on_select (GtkTreeSelection *selection, gpointer data)
239 {
240   PsppireAcr *acr = data;
241
242   gtk_widget_set_sensitive (acr->remove_button, row_is_selected (acr));
243
244   gtk_widget_set_sensitive (acr->change_button,
245                             row_is_selected (acr)
246                         );
247 }
248
249
250 void
251 psppire_acr_set_enabled (PsppireAcr *acr, gboolean status)
252 {
253
254   gtk_widget_set_sensitive (acr->add_button, status);
255
256   gtk_widget_set_sensitive (acr->change_button, status
257                             && row_is_selected (acr));
258 }
259
260 static void
261 psppire_acr_init (PsppireAcr *acr)
262 {
263   GtkWidget *bb  = gtk_button_box_new (GTK_ORIENTATION_VERTICAL);
264
265   GtkWidget *sw = gtk_scrolled_window_new (NULL, NULL);
266
267   acr->dispose_has_run = FALSE;
268
269   gtk_orientable_set_orientation (GTK_ORIENTABLE (acr), GTK_ORIENTATION_HORIZONTAL);
270
271   acr->tv = GTK_TREE_VIEW (gtk_tree_view_new ());
272
273   acr->add_button = gtk_button_new_with_label (_("Add"));
274   acr->change_button = gtk_button_new_with_label (_("Edit"));
275   acr->remove_button = gtk_button_new_with_label (_("Remove"));
276
277   acr->get_value = NULL;
278   acr->get_value_data = NULL;
279   acr->enabled = NULL;
280   acr->update = NULL;
281
282   gtk_widget_set_sensitive (acr->change_button, FALSE);
283   gtk_widget_set_sensitive (acr->remove_button, FALSE);
284   gtk_widget_set_sensitive (acr->add_button, FALSE);
285
286   psppire_box_pack_start_defaults (GTK_BOX (bb), acr->add_button);
287   psppire_box_pack_start_defaults (GTK_BOX (bb), acr->change_button);
288   psppire_box_pack_start_defaults (GTK_BOX (bb), acr->remove_button);
289
290   gtk_box_pack_start (GTK_BOX (acr), bb, FALSE, TRUE, 5);
291
292   g_object_set (sw,
293                 "hscrollbar-policy", GTK_POLICY_NEVER,
294                 "vscrollbar-policy", GTK_POLICY_AUTOMATIC,
295                 "shadow-type", GTK_SHADOW_ETCHED_IN,
296                 NULL);
297
298   gtk_container_add (GTK_CONTAINER (sw), GTK_WIDGET (acr->tv));
299
300   gtk_box_pack_start (GTK_BOX (acr), sw, TRUE, TRUE, 5);
301
302
303   g_signal_connect_swapped (acr->add_button, "clicked",
304                             G_CALLBACK (on_add_button_clicked), acr);
305   g_signal_connect_swapped (acr->change_button, "clicked",
306                             G_CALLBACK (on_change_button_clicked), acr);
307   g_signal_connect_swapped (acr->remove_button, "clicked",
308                             G_CALLBACK (on_remove_button_clicked), acr);
309
310   gtk_widget_show_all (bb);
311
312
313   g_object_set (acr->tv, "headers-visible", FALSE, NULL);
314
315   acr->list_store = NULL;
316
317   psppire_acr_set_model (acr, acr->list_store);
318
319   acr->selection = gtk_tree_view_get_selection (acr->tv);
320
321   g_signal_connect (acr->selection, "changed", G_CALLBACK (on_select), acr);
322
323   gtk_widget_set_sensitive (GTK_WIDGET (acr), FALSE);
324
325   gtk_widget_show_all (sw);
326
327   {
328     GtkCellRenderer *renderer = gtk_cell_renderer_text_new ();
329     GtkTreeViewColumn *column =
330       gtk_tree_view_column_new_with_attributes ("value",
331                                                 renderer,
332                                                 "text", 0,
333                                                 NULL);
334
335     gtk_tree_view_append_column (acr->tv, column);
336   }
337
338 }
339
340
341 GtkWidget*
342 psppire_acr_new (void)
343 {
344   return GTK_WIDGET (g_object_new (psppire_acr_get_type (), NULL));
345 }
346
347
348
349 /* Set the widget's treemodel to LISTSTORE.  LISTSTORE ownership is not
350    transferred. */
351 void
352 psppire_acr_set_model (PsppireAcr *acr, GtkListStore *liststore)
353 {
354   if (acr->list_store)
355     g_object_unref (acr->list_store);
356   if (liststore)
357     g_object_ref (liststore);
358
359   acr->list_store = liststore;
360
361   gtk_tree_view_set_model (GTK_TREE_VIEW (acr->tv),
362                            GTK_TREE_MODEL (liststore));
363
364   gtk_widget_set_sensitive (GTK_WIDGET (acr), liststore != NULL);
365 }
366
367
368 void
369 psppire_acr_set_enable_func (PsppireAcr *acr, EnabledFunc func, gpointer p)
370 {
371   acr->enabled = func;
372   acr->enabled_data = p;
373 }
374
375 void
376 psppire_acr_set_get_value_func (PsppireAcr *acr,
377                                 GetValueFunc getvalue, gpointer data)
378 {
379   acr->get_value_data = data;
380   acr->get_value = getvalue;
381 }