gui-message.c 14.9 KB
Newer Older
1
/* GIMP - The GNU Image Manipulation Program
2 3
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
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 8 9 10 11 12 13 14
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
15
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
16 17 18 19 20 21
 */

#include "config.h"

#include <string.h>

22
#include <gegl.h>
23 24
#include <gtk/gtk.h>

25
#include "libgimpbase/gimpbase.h"
26 27 28 29 30 31 32 33 34 35 36
#include "libgimpwidgets/gimpwidgets.h"

#include "gui-types.h"

#include "config/gimpguiconfig.h"

#include "core/gimp.h"
#include "core/gimpprogress.h"

#include "plug-in/gimpplugin.h"

37
#include "widgets/gimpcriticaldialog.h"
38 39 40 41
#include "widgets/gimpdialogfactory.h"
#include "widgets/gimpdockable.h"
#include "widgets/gimperrorconsole.h"
#include "widgets/gimperrordialog.h"
42
#include "widgets/gimpprogressdialog.h"
43
#include "widgets/gimpsessioninfo.h"
44
#include "widgets/gimpwidgets-utils.h"
45
#include "widgets/gimpwindowstrategy.h"
46

47 48
#include "about.h"

49 50
#include "gui-message.h"

51 52
#include "gimp-intl.h"

53

54 55 56
#define MAX_TRACES 3
#define MAX_ERRORS 10

57

58 59 60 61 62
typedef struct
{
  Gimp                *gimp;
  gchar               *domain;
  gchar               *message;
63
  gchar               *trace;
64 65 66 67
  GObject             *handler;
  GimpMessageSeverity  severity;
} GimpLogMessageData;

68

69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
static gboolean  gui_message_idle           (gpointer             user_data);
static gboolean  gui_message_error_console  (Gimp                *gimp,
                                             GimpMessageSeverity  severity,
                                             const gchar         *domain,
                                             const gchar         *message);
static gboolean  gui_message_error_dialog   (Gimp                *gimp,
                                             GObject             *handler,
                                             GimpMessageSeverity  severity,
                                             const gchar         *domain,
                                             const gchar         *message,
                                             const gchar         *trace);
static void      gui_message_console        (GimpMessageSeverity  severity,
                                             const gchar         *domain,
                                             const gchar         *message);
static gchar *   gui_message_format         (GimpMessageSeverity  severity,
                                             const gchar         *domain,
                                             const gchar         *message);

static GtkWidget * global_error_dialog      (void);
static GtkWidget * global_critical_dialog   (void);

90
static void        gui_message_reset_errors (GObject             *object,
91
                                             gpointer             user_data);
92

93

94 95 96 97 98
static GMutex mutex;
static gint   n_traces = 0;
static gint   n_errors = 0;


99
void
100 101 102 103
gui_message (Gimp                *gimp,
             GObject             *handler,
             GimpMessageSeverity  severity,
             const gchar         *domain,
104
             const gchar         *message)
105
{
106 107 108
  gchar    *trace     = NULL;
  gboolean  gen_trace = FALSE;

109 110 111
  switch (gimp->message_handler)
    {
    case GIMP_ERROR_CONSOLE:
112
      if (gui_message_error_console (gimp, severity, domain, message))
113 114 115 116 117 118
        return;

      gimp->message_handler = GIMP_MESSAGE_BOX;
      /*  fallthru  */

    case GIMP_MESSAGE_BOX:
119
      if (severity >= GIMP_MESSAGE_BUG_WARNING)
120 121 122 123 124 125 126 127 128 129 130 131 132
        {
          g_mutex_lock (&mutex);
          /* Trace creation can be time consuming so don't block the
           * mutex for too long and only increment and set a boolean
           * here.
           */
          if (n_traces < MAX_TRACES)
            {
              gen_trace = TRUE;
              n_traces++;
            }
          g_mutex_unlock (&mutex);
        }
133

134 135 136 137 138 139 140
      if (gen_trace)
        {
          /* We need to create the trace here because for multi-thread
           * errors (i.e. non-GIMP ones), the backtrace after a idle
           * function will simply be useless. It needs to happen in the
           * buggy thread to be meaningful.
           */
141
          gimp_stack_trace_print (NULL, NULL, &trace);
142 143
        }

144 145 146 147 148 149 150 151 152 153 154 155
      if (g_strcmp0 (GIMP_ACRONYM, domain) != 0)
        {
          /* Handle non-GIMP messages in a multi-thread safe way,
           * because we can't know for sure whether the log message may
           * not have been called from a thread other than the main one.
           */
          GimpLogMessageData *data;

          data = g_new0 (GimpLogMessageData, 1);
          data->gimp     = gimp;
          data->domain   = g_strdup (domain);
          data->message  = g_strdup (message);
156
          data->trace    = trace;
157 158 159 160 161 162 163 164
          data->handler  = handler? g_object_ref (handler) : NULL;
          data->severity = severity;

          gdk_threads_add_idle_full (G_PRIORITY_DEFAULT_IDLE,
                                     gui_message_idle,
                                     data, g_free);
          return;
        }
165
      if (gui_message_error_dialog (gimp, handler, severity,
166 167
                                    domain, message, trace))
        break;
168 169 170 171 172

      gimp->message_handler = GIMP_CONSOLE;
      /*  fallthru  */

    case GIMP_CONSOLE:
173
      gui_message_console (severity, domain, message);
174 175
      break;
    }
176 177
  if (trace)
    g_free (trace);
178 179
}

180 181 182 183 184 185 186 187 188
static gboolean
gui_message_idle (gpointer user_data)
{
  GimpLogMessageData *data = (GimpLogMessageData *) user_data;

  if (! gui_message_error_dialog (data->gimp,
                                  data->handler,
                                  data->severity,
                                  data->domain,
189 190
                                  data->message,
                                  data->trace))
191 192 193
    {
      gui_message_console (data->severity,
                           data->domain,
194
                           data->message);
195 196 197
    }
  g_free (data->domain);
  g_free (data->message);
198 199
  if (data->trace)
    g_free (data->trace);
200 201 202 203 204 205
  if (data->handler)
    g_object_unref (data->handler);

  return FALSE;
}

206
static gboolean
207 208
gui_message_error_console (Gimp                *gimp,
                           GimpMessageSeverity  severity,
209
                           const gchar         *domain,
210
                           const gchar         *message)
211
{
212 213 214 215
  GtkWidget *dockable;

  dockable = gimp_dialog_factory_find_widget (gimp_dialog_factory_get_singleton (),
                                              "gimp-error-console");
216

217
  /* avoid raising the error console for unhighlighted messages */
218
  if (dockable)
219
    {
220 221 222 223
      GtkWidget *child = gtk_bin_get_child (GTK_BIN (dockable));

      if (GIMP_ERROR_CONSOLE (child)->highlight[severity])
        dockable = NULL;
224 225 226
    }

  if (! dockable)
227
    {
228
      GdkMonitor *monitor = gimp_get_monitor_at_pointer ();
229 230 231 232 233

      dockable =
        gimp_window_strategy_show_dockable_dialog (GIMP_WINDOW_STRATEGY (gimp_get_window_strategy (gimp)),
                                                   gimp,
                                                   gimp_dialog_factory_get_singleton (),
234
                                                   monitor,
235 236
                                                   "gimp-error-console");
    }
237 238 239

  if (dockable)
    {
240 241 242
      GtkWidget *child = gtk_bin_get_child (GTK_BIN (dockable));

      gimp_error_console_add (GIMP_ERROR_CONSOLE (child),
243
                              severity, domain, message);
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285

      return TRUE;
    }

  return FALSE;
}

static void
progress_error_dialog_unset (GimpProgress *progress)
{
  g_object_set_data (G_OBJECT (progress), "gimp-error-dialog", NULL);
}

static GtkWidget *
progress_error_dialog (GimpProgress *progress)
{
  GtkWidget *dialog;

  g_return_val_if_fail (GIMP_IS_PROGRESS (progress), NULL);

  dialog = g_object_get_data (G_OBJECT (progress), "gimp-error-dialog");

  if (! dialog)
    {
      dialog = gimp_error_dialog_new (_("GIMP Message"));

      g_object_set_data (G_OBJECT (progress), "gimp-error-dialog", dialog);

      g_signal_connect_object (dialog, "destroy",
                               G_CALLBACK (progress_error_dialog_unset),
                               progress, G_CONNECT_SWAPPED);

      if (GTK_IS_WIDGET (progress))
        {
          GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (progress));

          if (GTK_IS_WINDOW (toplevel))
            gtk_window_set_transient_for (GTK_WINDOW (dialog),
                                          GTK_WINDOW (toplevel));
        }
      else
        {
286
          guint32 window_id = gimp_progress_get_window_id (progress);
287

288 289
          if (window_id)
            gimp_window_set_transient_for (GTK_WINDOW (dialog), window_id);
290 291 292 293 294 295 296
        }
    }

  return dialog;
}

static gboolean
297 298 299 300
gui_message_error_dialog (Gimp                *gimp,
                          GObject             *handler,
                          GimpMessageSeverity  severity,
                          const gchar         *domain,
301 302
                          const gchar         *message,
                          const gchar         *trace)
303
{
304 305 306 307 308
  GtkWidget      *dialog;
  GtkMessageType  type   = GTK_MESSAGE_ERROR;

  switch (severity)
    {
309 310 311 312 313 314 315 316 317 318 319 320 321
    case GIMP_MESSAGE_INFO:
      type = GTK_MESSAGE_INFO;
      break;
    case GIMP_MESSAGE_WARNING:
      type = GTK_MESSAGE_WARNING;
      break;
    case GIMP_MESSAGE_ERROR:
      type = GTK_MESSAGE_ERROR;
      break;
    case GIMP_MESSAGE_BUG_WARNING:
    case GIMP_MESSAGE_BUG_CRITICAL:
      type = GTK_MESSAGE_OTHER;
      break;
322 323
    }

324
  if (severity >= GIMP_MESSAGE_BUG_WARNING)
325
    {
326 327 328 329 330
      /* Process differently programming errors.
       * The reason is that we will generate traces, which will take
       * significant place, and cannot be processed as a progress
       * message or in the global dialog. It will require its own
       * dedicated dialog which will encourage people to report the bug.
331
       */
332 333 334 335 336 337 338 339 340
      gboolean gui_error = FALSE;

      g_mutex_lock (&mutex);
      if (n_errors < MAX_ERRORS)
        {
          gui_error = TRUE;
          n_errors++;
        }
      g_mutex_unlock (&mutex);
341

342
      if (gui_error || trace)
343 344 345
        {
          gchar *text;

346
          dialog = global_critical_dialog ();
347 348 349 350 351 352 353 354 355 356 357 358

          text = gui_message_format (severity, domain, message);
          gimp_critical_dialog_add (dialog, text, trace, FALSE, NULL, 0);

          gtk_widget_show (dialog);

          g_free (text);

          return TRUE;
        }
      else
        {
359 360 361 362 363
          const gchar *reason = "Message";

          gimp_enum_get_value (GIMP_TYPE_MESSAGE_SEVERITY, severity,
                               NULL, NULL, &reason, NULL);

364
          /* Since we overridden glib default's WARNING and CRITICAL
365 366 367 368 369
           * handler, if we decide not to handle this error in the end,
           * let's just print it in terminal in a similar fashion as
           * glib's default handler (though without the fancy terminal
           * colors right now).
           */
370
          g_printerr ("%s-%s: %s\n", domain, reason, message);
371 372 373

          return TRUE;
        }
374 375
    }
  else if (GIMP_IS_PROGRESS (handler))
376
    {
377 378 379 380 381
      /* If there's already an error dialog associated with this
       * progress, then continue without trying gimp_progress_message().
       */
      if (! g_object_get_data (handler, "gimp-error-dialog") &&
          gimp_progress_message (GIMP_PROGRESS (handler), gimp,
382 383 384 385 386 387 388
                                 severity, domain, message))
        {
          return TRUE;
        }
    }
  else if (GTK_IS_WIDGET (handler))
    {
389
      GtkWidget *parent = GTK_WIDGET (handler);
390 391 392 393 394

      dialog =
        gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (parent)),
                                GTK_DIALOG_DESTROY_WITH_PARENT,
                                type, GTK_BUTTONS_OK,
395
                                "%s", message);
396 397 398 399 400 401 402 403 404 405 406 407

      g_signal_connect (dialog, "response",
                        G_CALLBACK (gtk_widget_destroy),
                        NULL);

      gtk_widget_show (dialog);

      return TRUE;
    }

  if (GIMP_IS_PROGRESS (handler) && ! GIMP_IS_PROGRESS_DIALOG (handler))
    dialog = progress_error_dialog (GIMP_PROGRESS (handler));
408 409
  else
    dialog = global_error_dialog ();
410 411 412 413

  if (dialog)
    {
      gimp_error_dialog_add (GIMP_ERROR_DIALOG (dialog),
414
                             gimp_get_message_icon_name (severity),
415
                             domain, message);
416 417 418 419 420 421 422 423 424
      gtk_window_present (GTK_WINDOW (dialog));

      return TRUE;
    }

  return FALSE;
}

static void
425 426
gui_message_console (GimpMessageSeverity  severity,
                     const gchar         *domain,
427
                     const gchar         *message)
428 429 430 431 432 433 434 435 436 437 438 439
{
  gchar *formatted_message;

  formatted_message = gui_message_format (severity, domain, message);
  g_printerr ("%s\n\n", formatted_message);
  g_free (formatted_message);
}

static gchar *
gui_message_format (GimpMessageSeverity  severity,
                    const gchar         *domain,
                    const gchar         *message)
440
{
441
  const gchar *desc = "Message";
442
  gchar       *formatted_message;
443 444 445

  gimp_enum_get_value (GIMP_TYPE_MESSAGE_SEVERITY, severity,
                       NULL, NULL, &desc, NULL);
446 447 448 449

  formatted_message = g_strdup_printf ("%s-%s: %s", domain, desc, message);

  return formatted_message;
450
}
451 452 453 454

static GtkWidget *
global_error_dialog (void)
{
455
  GdkMonitor *monitor = gimp_get_monitor_at_pointer ();
456 457

  return gimp_dialog_factory_dialog_new (gimp_dialog_factory_get_singleton (),
458
                                         monitor,
459
                                         NULL /*ui_manager*/,
460
                                         NULL,
461 462 463 464 465 466 467
                                         "gimp-error-dialog", -1,
                                         FALSE);
}

static GtkWidget *
global_critical_dialog (void)
{
468 469
  GdkMonitor *monitor = gimp_get_monitor_at_pointer ();
  GtkWidget  *dialog;
470 471

  dialog = gimp_dialog_factory_dialog_new (gimp_dialog_factory_get_singleton (),
472
                                           monitor,
473
                                           NULL /*ui_manager*/,
474
                                           NULL,
475 476 477 478 479 480 481 482 483 484 485 486
                                           "gimp-critical-dialog", -1,
                                           FALSE);
  g_signal_handlers_disconnect_by_func (dialog,
                                        gui_message_reset_errors,
                                        NULL);
  g_signal_connect (dialog, "destroy",
                    G_CALLBACK (gui_message_reset_errors),
                    NULL);
  return dialog;
}

static void
487 488
gui_message_reset_errors (GObject  *object,
                          gpointer  user_data)
489 490 491 492 493 494
{
  g_mutex_lock (&mutex);
  n_errors = 0;
  n_traces = 0;
  g_mutex_unlock (&mutex);
}