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