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
23 #include <sys/types.h>
40 #ifdef HAVE_LIBDMALLOC
44 #ifdef HAVE_G_REGEX_NEW
45 #define USE_OBJECT_HYPERLINKS
48 struct global_page_search
{
49 /* Look for this string. */
52 /* Record the search progress here. */
54 GSCHEM_TOPLEVEL
*window
;
58 /* Carry some widget context too. */
63 static void x_log_callback_response (GtkDialog
*dialog
,
66 static void log_message (Log
*log
,
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!!!
80 * \par Function Description
85 if (log_dialog
== NULL
) {
88 log_dialog
= GTK_WIDGET (g_object_new (TYPE_LOG
,
90 "settings-name", "log",
91 /* "toplevel", TOPLEVEL * */
93 "type", GTK_WINDOW_TOPLEVEL
,
96 g_signal_connect (log_dialog
,
98 G_CALLBACK (x_log_callback_response
),
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
)
111 log_message (LOG (log_dialog
), contents
, "old");
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
);
120 g_assert (IS_LOG (log_dialog
));
121 gtk_window_present ((GtkWindow
*)log_dialog
);
125 /*! \todo Finish function documentation!!!
127 * \par Function Description
133 g_assert (IS_LOG (log_dialog
));
134 gtk_widget_destroy (log_dialog
);
135 x_log_update_func
= NULL
;
140 /*! \todo Finish function documentation!!!
142 * \par Function Description
145 void x_log_message (const gchar
*log_domain
, GLogLevelFlags log_level
,
146 const gchar
*message
)
149 g_return_if_fail (log_dialog
!= NULL
);
151 if (log_level
& (G_LOG_LEVEL_CRITICAL
| G_LOG_LEVEL_ERROR
)) {
153 } else if (log_level
& G_LOG_LEVEL_WARNING
) {
159 log_message (LOG(log_dialog
), message
, style
);
162 /*! \todo Finish function documentation!!!
164 * \par Function Description
167 static void x_log_callback_response (GtkDialog
*dialog
,
172 case GTK_RESPONSE_DELETE_EVENT
:
173 case LOG_RESPONSE_CLOSE
:
174 g_assert (GTK_WIDGET (dialog
) == log_dialog
);
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
;
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
;
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
;
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
,
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
;
235 /* Can't be an object hyperlink tag. */
240 search
.found
= FALSE
;
241 g_list_foreach(global_window_list
, &window_has_object
, &search
);
244 o_select_object_simple(search
.window
, search
.window
->toplevel
,
245 search
.page
, search
.o
, TRUE
);
254 static void x_log_textview_event_after(GtkWidget
*text_view
,
257 /* Copied from GTK+'s demos/gtk-demo/hypertext.c then modified. */
258 GtkTextIter start
, end
, iter
;
259 GtkTextBuffer
*buffer
;
260 GdkEventButton
*event
;
263 if (ev
->type
!= GDK_BUTTON_RELEASE
) {
267 event
= (GdkEventButton
*) ev
;
269 if (event
->button
!= 1) {
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
)) {
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
;
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
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
));
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
);
327 int start_pos
, end_pos
;
328 GtkTextTag
*link_tag
;
329 GMatchInfo
*object_match
= NULL
;
332 if (!g_regex_match_full(klass
->link_re
, message
,
333 -1, last_end
, 0, &object_match
, NULL
)) {
334 /* No more object names. */
336 g_free(object_match
);
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",
345 /* Just pretend there was no match after all. */
346 g_free(object_match
);
350 name
= g_match_info_fetch(object_match
, 1);
352 g_free(object_match
);
356 "Can't extract object name from matched string \"%.*s\"\n",
357 end_pos
- start_pos
, message
+ start_pos
);
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
,
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
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
);
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!!!
399 * \par Function Description
402 static void log_message (Log
*log
, const gchar
*message
,
405 GtkTextBuffer
*buffer
;
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 */
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!!!
440 * \par Function Description
443 GType
log_get_type ()
445 static GType log_type
= 0;
448 static const GTypeInfo log_info
= {
450 (GBaseInitFunc
) &log_base_init
,
451 (GBaseFinalizeFunc
) &log_base_finalize
,
452 (GClassInitFunc
) log_class_init
,
453 NULL
, /* class_finalize */
454 NULL
, /* class_data */
457 (GInstanceInitFunc
) log_init
,
460 log_type
= g_type_register_static (GSCHEM_TYPE_DIALOG
,
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!!!
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!!!
494 * \par Function Description
497 static void log_init (Log
*log
)
499 GtkWidget
*scrolled_win
, *text_view
;
500 GtkTextBuffer
*text_buffer
;
503 /* dialog initialization */
504 g_object_set (G_OBJECT (log
),
508 "title", _("Status"),
509 "default-width", 600,
510 "default-height", 200,
512 "window-position", GTK_WIN_POS_NONE
,
513 "type-hint", GDK_WINDOW_TYPE_HINT_NORMAL
,
515 "has-separator", TRUE
,
518 /* create a scrolled window for the textview */
519 scrolled_win
= GTK_WIDGET (
520 g_object_new (GTK_TYPE_SCROLLED_WINDOW
,
523 /* GtkScrolledWindow */
524 "hscrollbar-policy", GTK_POLICY_AUTOMATIC
,
525 "vscrollbar-policy", GTK_POLICY_AUTOMATIC
,
526 "shadow-type", GTK_SHADOW_ETCHED_IN
,
528 /* create the text buffer */
529 text_buffer
= GTK_TEXT_BUFFER (g_object_new (GTK_TYPE_TEXT_BUFFER
,
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
,
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
,
546 /* "warning" messages are printed in red */
547 gtk_text_buffer_create_tag (text_buffer
, "warning",
549 "foreground-set", TRUE
,
551 /* "critical" messages are bold red */
552 gtk_text_buffer_create_tag (text_buffer
, "critical",
554 "foreground-set", TRUE
,
555 "weight", PANGO_WEIGHT_BOLD
,
559 /* create the text view and attach the buffer to it */
560 text_view
= GTK_WIDGET (g_object_new (GTK_TYPE_TEXT_VIEW
,
562 /* unknown property in GTK 2.2, use gtk_text_view_set_buffer() instead */
563 /* "buffer", text_buffer, */
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
,
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
));