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