Fix crash in find dialog and make code less horrible.
[pspp] / src / ui / gui / message-dialog.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2004, 2005 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 #include <stdio.h>
19 #include <stdarg.h>
20
21 #include <config.h>
22 #include <gettext.h>
23 #define _(msgid) gettext (msgid)
24 #define N_(msgid) msgid
25
26 #include <libpspp/message.h>
27 #include <libpspp/str.h>
28 #include <libpspp/msg-locator.h>
29 #include "message-dialog.h"
30 #include "progname.h"
31
32
33 #include <gtk/gtk.h>
34 #include <glib.h>
35
36 #include "helper.h"
37
38 static void enqueue_msg (const struct msg *m);
39 static gboolean popup_messages (gpointer);
40
41 #define MAX_EARLY_MESSAGES 100
42 static GQueue *early_queue;
43
44 static unsigned long dropped_messages;
45
46 #define MAX_LATE_MESSAGES 10
47 static GQueue *late_queue;
48
49 static int error_cnt, warning_cnt, note_cnt;
50
51 static GtkBuilder *message_xml;
52 static GtkWidget *message_dialog;
53
54 void
55 message_dialog_init (struct source_stream *ss)
56 {
57   early_queue = g_queue_new ();
58   dropped_messages = 0;
59   late_queue = g_queue_new ();
60   error_cnt = warning_cnt = note_cnt = 0;
61   msg_init (ss, enqueue_msg);
62   message_xml = builder_new ("message-dialog.ui");
63   message_dialog = get_widget_assert (message_xml, "message-dialog");
64
65   GTK_WIDGET_SET_FLAGS (get_widget_assert (message_xml, "close-button"),
66                         GTK_CAN_DEFAULT);
67
68 }
69
70 void
71 message_dialog_done (void)
72 {
73   msg_done ();
74   g_queue_free (early_queue);
75   dropped_messages = 0;
76   g_queue_free (late_queue);
77   gtk_widget_destroy (message_dialog);
78   g_object_unref (message_xml);
79 }
80
81 static void
82 format_message (struct msg *m, struct string *msg)
83 {
84   const char *label;
85
86   if (m->where.file_name)
87     ds_put_format (msg, "%s:", m->where.file_name);
88   if (m->where.line_number != -1)
89     ds_put_format (msg, "%d:", m->where.line_number);
90   if (m->where.file_name || m->where.line_number != -1)
91     ds_put_char (msg, ' ');
92
93   switch (m->severity)
94     {
95     case MSG_ERROR:
96       switch (m->category)
97         {
98         case MSG_SYNTAX:
99           label = _("syntax error");
100           break;
101
102         case MSG_DATA:
103           label = _("data file error");
104           break;
105
106         case MSG_GENERAL:
107         default:
108           label = _("PSPP error");
109           break;
110         }
111       break;
112     case MSG_WARNING:
113       switch (m->category)
114         {
115         case MSG_SYNTAX:
116           label = _("syntax warning");
117           break;
118
119         case MSG_DATA:
120           label = _("data file warning");
121           break;
122
123         case MSG_GENERAL:
124         default:
125           label = _("PSPP warning");
126           break;
127         }
128       break;
129     case MSG_NOTE:
130     default:
131       switch (m->category)
132         {
133         case MSG_SYNTAX:
134           label = _("syntax information");
135           break;
136
137         case MSG_DATA:
138           label = _("data file information");
139           break;
140
141         case MSG_GENERAL:
142         default:
143           label = _("PSPP information");
144           break;
145         }
146       break;
147     }
148   ds_put_format (msg, "%s: %s\n", label, m->text);
149   msg_destroy (m);
150 }
151
152 static void
153 enqueue_msg (const struct msg *msg)
154 {
155   struct msg *m = msg_dup (msg);
156
157   switch (m->severity)
158     {
159     case MSG_ERROR:
160       error_cnt++;
161       break;
162     case MSG_WARNING:
163       warning_cnt++;
164       break;
165     case MSG_NOTE:
166       note_cnt++;
167       break;
168     }
169
170   if (g_queue_get_length (early_queue) < MAX_EARLY_MESSAGES)
171     {
172       if (g_queue_is_empty (early_queue))
173         g_idle_add (popup_messages, NULL);
174       g_queue_push_tail (early_queue, m);
175     }
176   else
177     {
178       if (g_queue_get_length (late_queue) >= MAX_LATE_MESSAGES)
179         {
180           struct msg *m = g_queue_pop_head (late_queue);
181           msg_destroy (m);
182           dropped_messages++;
183         }
184       g_queue_push_tail (late_queue, m);
185     }
186 }
187
188 static gboolean
189 popup_messages (gpointer unused UNUSED)
190 {
191   GtkTextBuffer *text_buffer;
192   GtkTextIter end;
193   GtkTextView *text_view;
194   GtkLabel *label;
195   struct string lead = DS_EMPTY_INITIALIZER;
196   struct string msg = DS_EMPTY_INITIALIZER;
197   int message_cnt;
198
199   gdk_threads_enter ();
200
201   /* Set up the dialog. */
202   if (message_xml == NULL || message_dialog == NULL)
203     goto use_fallback;
204
205   /* If a pointer grab is in effect, then the combination of that, and
206      a modal dialog box, will cause an impossible situation.
207      So don't pop it up just yet.
208   */
209   if ( gdk_display_pointer_is_grabbed (gtk_widget_get_display (message_dialog)))
210     {
211       ds_destroy (&lead);
212       ds_destroy (&msg);
213       gdk_threads_leave ();
214       return TRUE;
215     }
216
217   /* Compose the lead-in. */
218   message_cnt = error_cnt + warning_cnt + note_cnt;
219   if (dropped_messages == 0)
220     ds_put_format (
221       &lead,
222       ngettext ("The PSPP processing engine reported the following message:",
223                 "The PSPP processing engine reported the following messages:",
224                 message_cnt));
225   else
226     {
227       ds_put_format (
228         &lead,
229         ngettext ("The PSPP processing engine reported %d message.",
230                   "The PSPP processing engine reported %d messages.",
231                   message_cnt),
232         message_cnt);
233       ds_put_cstr (&lead, "  ");
234       ds_put_format (
235         &lead,
236         ngettext ("%d of these messages are displayed below.",
237                   "%d of these messages are displayed below.",
238                   MAX_EARLY_MESSAGES + MAX_LATE_MESSAGES),
239         MAX_EARLY_MESSAGES + MAX_LATE_MESSAGES);
240     }
241
242
243   /* Compose the messages. */
244   while (!g_queue_is_empty (early_queue))
245     format_message (g_queue_pop_head (early_queue), &msg);
246   if (dropped_messages)
247     {
248       ds_put_format (&msg, "...\nOmitting %lu messages\n...\n",
249                      dropped_messages);
250       dropped_messages = 0;
251     }
252   while (!g_queue_is_empty (late_queue))
253     format_message (g_queue_pop_head (late_queue), &msg);
254
255   text_buffer = gtk_text_buffer_new (NULL);
256   gtk_text_buffer_get_end_iter (text_buffer, &end);
257   gtk_text_buffer_insert (text_buffer, &end, ds_data (&msg), ds_length (&msg));
258
259   label = GTK_LABEL (get_widget_assert (message_xml, "lead-in"));
260   if (label == NULL)
261     goto use_fallback;
262   gtk_label_set_text (label, ds_cstr (&lead));
263
264   text_view = GTK_TEXT_VIEW (get_widget_assert (message_xml, "message"));
265   if (text_view == NULL)
266     goto use_fallback;
267   gtk_text_view_set_buffer (text_view, text_buffer);
268
269   gtk_widget_grab_default (get_widget_assert (message_xml, "close-button"));
270   gtk_widget_grab_focus (get_widget_assert (message_xml, "close-button"));
271   gtk_dialog_run ( GTK_DIALOG (message_dialog));
272   gtk_widget_hide (message_dialog);
273
274   ds_destroy (&lead);
275   ds_destroy (&msg);
276
277   gdk_threads_leave ();
278   return FALSE;
279
280 use_fallback:
281   g_warning ("Could not create message dialog.  "
282              "Is PSPPIRE properly installed?");
283   fputs (ds_cstr (&msg), stderr);
284   ds_destroy (&lead);
285   ds_destroy (&msg);
286   gdk_threads_leave ();
287   return FALSE;
288 }
289