Take page as input and take advantage of s_visit_page.
[geda-gaf/berndj.git] / gschem / src / x_log.c
blob84d754a1d036a5bcd5d566e15a5854bde4b6f875
1 /* gEDA - GPL Electronic Design Automation
2 * gschem - gEDA Schematic Capture
3 * Copyright (C) 1998-2007 Ales Hvezda
4 * Copyright (C) 1998-2007 gEDA Contributors (see ChangeLog for details)
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA
20 #include <config.h>
22 #include <stdio.h>
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #ifdef HAVE_STDLIB_H
26 #include <stdlib.h>
27 #endif
28 #ifdef HAVE_UNISTD_H
29 #include <unistd.h>
30 #endif
31 #ifdef HAVE_FCNTL_H
32 #include <fcntl.h>
33 #endif
34 #ifdef HAVE_STRING_H
35 #include <string.h>
36 #endif
38 #include "gschem.h"
40 #ifdef HAVE_LIBDMALLOC
41 #include <dmalloc.h>
42 #endif
44 #ifdef HAVE_G_REGEX_NEW
45 #define USE_OBJECT_HYPERLINKS
46 #endif
48 struct global_page_search {
49 /* Look for this string. */
50 char const *key;
52 /* Record the search progress here. */
53 gboolean found;
54 GSCHEM_TOPLEVEL *window;
55 PAGE *page;
56 OBJECT *o;
58 /* Carry some widget context too. */
59 Log *log;
60 GtkTextTag *link_tag;
63 static void x_log_callback_response (GtkDialog *dialog,
64 gint arg1,
65 gpointer user_data);
66 static void log_message (Log *log,
67 const gchar *message,
68 const gchar *style);
70 static void log_base_init(LogClass *klass);
71 static void log_base_finalize(LogClass *klass);
72 static void log_class_init (LogClass *class);
73 static void log_init (Log *log);
75 static GtkWidget *log_dialog = NULL;
76 static GObjectClass *log_parent_class = NULL;
78 /*! \todo Finish function documentation!!!
79 * \brief
80 * \par Function Description
83 void x_log_open ()
85 if (log_dialog == NULL) {
86 gchar *contents;
88 log_dialog = GTK_WIDGET (g_object_new (TYPE_LOG,
89 /* GschemDialog */
90 "settings-name", "log",
91 /* "toplevel", TOPLEVEL * */
92 /* GtkDialog */
93 "type", GTK_WINDOW_TOPLEVEL,
94 NULL));
96 g_signal_connect (log_dialog,
97 "response",
98 G_CALLBACK (x_log_callback_response),
99 NULL);
101 /* make it read the content of the current log file */
102 /* and add its contents to the dialog */
103 contents = s_log_read ();
105 /* s_log_read can return NULL if the log file cannot be written to */
106 if (contents == NULL)
108 return;
111 log_message (LOG (log_dialog), contents, "old");
112 g_free (contents);
114 x_log_update_func = x_log_message;
116 if( auto_place_mode )
117 gtk_widget_set_uposition( log_dialog, 10, 10);
118 gtk_widget_show (log_dialog);
119 } else {
120 g_assert (IS_LOG (log_dialog));
121 gtk_window_present ((GtkWindow*)log_dialog);
125 /*! \todo Finish function documentation!!!
126 * \brief
127 * \par Function Description
130 void x_log_close ()
132 if (log_dialog) {
133 g_assert (IS_LOG (log_dialog));
134 gtk_widget_destroy (log_dialog);
135 x_log_update_func = NULL;
136 log_dialog = NULL;
140 /*! \todo Finish function documentation!!!
141 * \brief
142 * \par Function Description
145 void x_log_message (const gchar *log_domain, GLogLevelFlags log_level,
146 const gchar *message)
148 gchar *style;
149 g_return_if_fail (log_dialog != NULL);
151 if (log_level & (G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_ERROR)) {
152 style = "critical";
153 } else if (log_level & G_LOG_LEVEL_WARNING) {
154 style = "warning";
155 } else {
156 style = "message";
159 log_message (LOG(log_dialog), message, style);
162 /*! \todo Finish function documentation!!!
163 * \brief
164 * \par Function Description
167 static void x_log_callback_response (GtkDialog *dialog,
168 gint arg1,
169 gpointer user_data)
171 switch (arg1) {
172 case GTK_RESPONSE_DELETE_EVENT:
173 case LOG_RESPONSE_CLOSE:
174 g_assert (GTK_WIDGET (dialog) == log_dialog);
175 x_log_close ();
176 break;
177 default:
178 g_assert_not_reached ();
182 #ifdef USE_OBJECT_HYPERLINKS
183 static void object_has_name(OBJECT *o_current, void *userdata)
185 struct global_page_search *search = userdata;
187 if (search->found) {
188 return;
191 if (strcmp(o_current->name, search->key) == 0) {
192 search->o = o_current;
193 search->found = TRUE;
197 static void page_has_object(PAGE *page, void *userdata)
199 struct global_page_search *search = userdata;
201 if (search->found) {
202 return;
205 search->page = page;
206 s_visit_page(page, &object_has_name, search, VISIT_UNORDERED, 1);
209 static void window_has_object(void *list_data, void *userdata)
211 GSCHEM_TOPLEVEL *window = list_data;
212 struct global_page_search *search = userdata;
214 if (search->found) {
215 return;
218 search->window = window;
219 s_toplevel_foreach_page(window->toplevel, &page_has_object, search);
222 static void check_text_tags(GtkTextView *text_view G_GNUC_UNUSED,
223 GtkTextIter *iter)
225 /* Copied from GTK+'s demos/gtk-demo/hypertext.c then modified. */
226 GSList *tags = NULL, *tagp = NULL;
228 tags = gtk_text_iter_get_tags(iter);
229 for (tagp = tags; tagp != NULL; tagp = tagp->next) {
230 GtkTextTag *tag = tagp->data;
231 char *name = g_object_get_data(G_OBJECT(tag), "object_name");
232 struct global_page_search search;
234 if (!name) {
235 /* Can't be an object hyperlink tag. */
236 continue;
239 search.key = name;
240 search.found = FALSE;
241 g_list_foreach(global_window_list, &window_has_object, &search);
243 if (search.found) {
244 o_select_object_simple(search.window, search.window->toplevel,
245 search.page, search.o, TRUE);
249 if (tags) {
250 g_slist_free (tags);
254 static void x_log_textview_event_after(GtkWidget *text_view,
255 GdkEvent *ev)
257 /* Copied from GTK+'s demos/gtk-demo/hypertext.c then modified. */
258 GtkTextIter start, end, iter;
259 GtkTextBuffer *buffer;
260 GdkEventButton *event;
261 gint x, y;
263 if (ev->type != GDK_BUTTON_RELEASE) {
264 return;
267 event = (GdkEventButton *) ev;
269 if (event->button != 1) {
270 return;
273 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_view));
276 * We get button-release-event even when it's a click-drag-release sequence
277 * to select some text. Tracking motion events between click and release
278 * seems like a lot of effort, so rather just use the presence of a text
279 * selection as a proxy for motion. TODO: Track motion events.
281 gtk_text_buffer_get_selection_bounds(buffer, &start, &end);
282 if (gtk_text_iter_get_offset(&start) != gtk_text_iter_get_offset(&end)) {
283 return;
286 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text_view),
287 GTK_TEXT_WINDOW_WIDGET,
288 event->x, event->y, &x, &y);
290 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(text_view), &iter, x, y);
292 check_text_tags(GTK_TEXT_VIEW(text_view), &iter);
295 static void link_tag_finalized(void *userdata, GObject *ex_tag G_GNUC_UNUSED)
297 char *name = userdata;
299 g_free(name);
302 /*! \brief Insert text into the log, with hyperlinks.
303 * \par Function Description
304 * Inserts text into \a buffer, applying tags to correctly formatted object
305 * names where they appear. Object names need only have the correct format
306 * in order to become hyperlinks.
308 * \note I can't easily tell if it's safe to cause log output from inside
309 * this function. Hence fprintf to stderr seems like a reasonable
310 * alternative.
312 static void insert_with_links(Log *log, GtkTextBuffer *buffer,
313 GtkTextIter *iter, gchar const *message,
314 gchar const *tag_name, gchar const *style)
316 LogClass *klass = LOG_CLASS(LOG_GET_CLASS(log));
317 int last_end;
318 GtkTextTagTable *tag_table;
319 GtkTextTag *base_tag, *style_tag;
321 tag_table = gtk_text_buffer_get_tag_table(buffer);
322 base_tag = gtk_text_tag_table_lookup(tag_table, tag_name);
323 style_tag = gtk_text_tag_table_lookup(tag_table, style);
325 last_end = 0;
326 while (1) {
327 int start_pos, end_pos;
328 GtkTextTag *link_tag;
329 GMatchInfo *object_match = NULL;
330 char *name;
332 if (!g_regex_match_full(klass->link_re, message,
333 -1, last_end, 0, &object_match, NULL)) {
334 /* No more object names. */
335 if (object_match) {
336 g_free(object_match);
338 break;
341 g_match_info_fetch_pos(object_match, 0, &start_pos, &end_pos);
342 if (start_pos == -1 || end_pos == -1) {
343 fprintf(stderr, "Can't get offsets for matched string \"%s\"\n",
344 message + last_end);
345 /* Just pretend there was no match after all. */
346 g_free(object_match);
347 continue;
350 name = g_match_info_fetch(object_match, 1);
351 if (object_match) {
352 g_free(object_match);
354 if (!name) {
355 fprintf(stderr,
356 "Can't extract object name from matched string \"%.*s\"\n",
357 end_pos - start_pos, message + start_pos);
358 continue;
361 if (start_pos - last_end) {
362 /* Push out normal text. */
363 gtk_text_buffer_insert_with_tags(buffer, iter,
364 message + last_end, start_pos - last_end,
365 base_tag, style_tag, NULL);
368 link_tag = gtk_text_buffer_create_tag(buffer, NULL,
369 "foreground", "navy blue",
370 "underline", PANGO_UNDERLINE_SINGLE,
371 NULL);
372 g_object_weak_ref(G_OBJECT(link_tag), &link_tag_finalized, name);
375 * Store name instead of a pointer to the object, otherwise we need a
376 * lot of weak reference spaghetti, and then also some signal spaghetti
377 * in order to notice new objects whose names may already appear in the
378 * log.
380 g_object_set_data(G_OBJECT(link_tag), "object_name", name);
381 /* TODO: Connect to "event" on link_tag. */
383 /* Push out the link. */
384 gtk_text_buffer_insert_with_tags(buffer, iter,
385 message + start_pos, end_pos - start_pos,
386 base_tag, style_tag, link_tag, NULL);
388 last_end = end_pos;
391 /* Push out any remaining text. */
392 gtk_text_buffer_insert_with_tags(buffer, iter, message + last_end, -1,
393 base_tag, style_tag, NULL);
395 #endif /* USE_OBJECT_HYPERLINKS */
397 /*! \todo Finish function documentation!!!
398 * \brief
399 * \par Function Description
402 static void log_message (Log *log, const gchar *message,
403 const gchar *style)
405 GtkTextBuffer *buffer;
406 GtkTextIter iter;
407 GtkTextMark *mark;
409 g_return_if_fail (IS_LOG (log));
411 buffer = gtk_text_view_get_buffer (log->textview);
412 gtk_text_buffer_get_end_iter (buffer, &iter);
414 /* Apply the "plain" tag before the level-specific tag in order to
415 * reset the formatting */
416 if (g_utf8_validate (message, -1, NULL)) {
417 #ifdef USE_OBJECT_HYPERLINKS
418 insert_with_links(log, buffer, &iter, message, "plain", style);
419 #else /* !USE_OBJECT_HYPERLINKS */
420 gtk_text_buffer_insert_with_tags_by_name (buffer, &iter, message, -1,
421 "plain", style, NULL);
422 #endif /* !USE_OBJECT_HYPERLINKS */
423 } else {
424 /* If UTF-8 wasn't valid (due to a system locale encoded filename or
425 * other string being included by mistake), log a warning, and print
426 * the original message to stderr, where it may be partly intelligible */
427 gtk_text_buffer_insert_with_tags_by_name (buffer, &iter,
428 _("** Invalid UTF-8 in log message. See stderr or gschem.log.\n"),
429 -1, "plain", style, NULL);
430 fprintf (stderr, "%s", message);
433 mark = gtk_text_buffer_create_mark(buffer, NULL, &iter, FALSE);
434 gtk_text_view_scroll_to_mark (log->textview, mark, 0, TRUE, 0, 1);
435 gtk_text_buffer_delete_mark (buffer, mark);
438 /*! \todo Finish function documentation!!!
439 * \brief
440 * \par Function Description
443 GType log_get_type ()
445 static GType log_type = 0;
447 if (!log_type) {
448 static const GTypeInfo log_info = {
449 sizeof(LogClass),
450 (GBaseInitFunc) &log_base_init,
451 (GBaseFinalizeFunc) &log_base_finalize,
452 (GClassInitFunc) log_class_init,
453 NULL, /* class_finalize */
454 NULL, /* class_data */
455 sizeof(Log),
456 0, /* n_preallocs */
457 (GInstanceInitFunc) log_init,
460 log_type = g_type_register_static (GSCHEM_TYPE_DIALOG,
461 "Log",
462 &log_info, 0);
465 return log_type;
468 static void log_base_init(LogClass *klass)
470 #ifdef USE_OBJECT_HYPERLINKS
471 klass->link_re = g_regex_new("#<object\\s+(\\w+\\.\\d+)>", 0, 0, NULL);
472 #endif /* USE_OBJECT_HYPERLINKS */
475 static void log_base_finalize(LogClass *klass)
477 #ifdef USE_OBJECT_HYPERLINKS
478 g_regex_unref(klass->link_re);
479 #endif /* USE_OBJECT_HYPERLINKS */
482 /*! \todo Finish function documentation!!!
483 * \brief
484 * \par Function Description
487 static void log_class_init (LogClass *klass)
489 log_parent_class = g_type_class_peek_parent(klass);
492 /*! \todo Finish function documentation!!!
493 * \brief
494 * \par Function Description
497 static void log_init (Log *log)
499 GtkWidget *scrolled_win, *text_view;
500 GtkTextBuffer *text_buffer;
501 GtkTextMark *mark;
503 /* dialog initialization */
504 g_object_set (G_OBJECT (log),
505 /* GtkContainer */
506 "border-width", 0,
507 /* GtkWindow */
508 "title", _("Status"),
509 "default-width", 600,
510 "default-height", 200,
511 "modal", FALSE,
512 "window-position", GTK_WIN_POS_NONE,
513 "type-hint", GDK_WINDOW_TYPE_HINT_NORMAL,
514 /* GtkDialog */
515 "has-separator", TRUE,
516 NULL);
518 /* create a scrolled window for the textview */
519 scrolled_win = GTK_WIDGET (
520 g_object_new (GTK_TYPE_SCROLLED_WINDOW,
521 /* GtkContainer */
522 "border-width", 5,
523 /* GtkScrolledWindow */
524 "hscrollbar-policy", GTK_POLICY_AUTOMATIC,
525 "vscrollbar-policy", GTK_POLICY_AUTOMATIC,
526 "shadow-type", GTK_SHADOW_ETCHED_IN,
527 NULL));
528 /* create the text buffer */
529 text_buffer = GTK_TEXT_BUFFER (g_object_new (GTK_TYPE_TEXT_BUFFER,
530 NULL));
532 /* Add some tags for highlighting log messages to the buffer */
533 gtk_text_buffer_create_tag (text_buffer, "plain",
534 "foreground", "black",
535 "foreground-set", TRUE,
536 "weight", PANGO_WEIGHT_NORMAL,
537 "weight-set", TRUE,
538 NULL);
539 /* The default "message" style is plain */
540 gtk_text_buffer_create_tag (text_buffer, "message", NULL);
541 /* "old" messages are in dark grey */
542 gtk_text_buffer_create_tag (text_buffer, "old",
543 "foreground", "#404040",
544 "foreground-set", TRUE,
545 NULL);
546 /* "warning" messages are printed in red */
547 gtk_text_buffer_create_tag (text_buffer, "warning",
548 "foreground", "red",
549 "foreground-set", TRUE,
550 NULL);
551 /* "critical" messages are bold red */
552 gtk_text_buffer_create_tag (text_buffer, "critical",
553 "foreground", "red",
554 "foreground-set", TRUE,
555 "weight", PANGO_WEIGHT_BOLD,
556 "weight-set", TRUE,
557 NULL);
559 /* create the text view and attach the buffer to it */
560 text_view = GTK_WIDGET (g_object_new (GTK_TYPE_TEXT_VIEW,
561 /* GtkTextView */
562 /* unknown property in GTK 2.2, use gtk_text_view_set_buffer() instead */
563 /* "buffer", text_buffer, */
564 "editable", FALSE,
565 NULL));
566 gtk_text_view_set_buffer (GTK_TEXT_VIEW (text_view), text_buffer);
568 #ifdef USE_OBJECT_HYPERLINKS
569 /* Use event-after so that GtkTextView can censor button-release-event. */
570 g_signal_connect(text_view, "event-after",
571 G_CALLBACK(&x_log_textview_event_after), NULL);
572 #endif /* USE_OBJECT_HYPERLINKS */
574 /* add the text view to the scrolled window */
575 gtk_container_add (GTK_CONTAINER (scrolled_win), text_view);
576 /* set textview of log */
577 log->textview = GTK_TEXT_VIEW (text_view);
579 /* add the scrolled window to the dialog vbox */
580 gtk_box_pack_start (GTK_BOX (GTK_DIALOG (log)->vbox), scrolled_win,
581 TRUE, TRUE, 0);
582 gtk_widget_show_all (scrolled_win);
584 /* now add the close button to the action area */
585 gtk_dialog_add_button (GTK_DIALOG (log),
586 GTK_STOCK_CLOSE, LOG_RESPONSE_CLOSE);
588 /* scroll to the end of the buffer */
589 mark = gtk_text_buffer_get_insert (text_buffer);
590 gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (text_view), mark, 0.0, TRUE, 0.0, 1.0);
592 g_object_unref(G_OBJECT(text_buffer));