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