docs: Document Open dialog options (#2355)
[geany-mirror.git] / src / msgwindow.c
blobce706cc45fff4e94e8081e7877cede6d4671a2ec
1 /*
2 * msgwindow.c - this file is part of Geany, a fast and lightweight IDE
4 * Copyright 2005 The Geany contributors
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 along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 /**
22 * @file msgwindow.h
23 * Message window functions (status, compiler, messages windows).
24 * Also compiler error message parsing and grep file and line parsing.
26 * @see GeanyMainWidgets::message_window_notebook to append a new notebook page.
27 **/
29 #ifdef HAVE_CONFIG_H
30 # include "config.h"
31 #endif
33 #include "msgwindow.h"
35 #include "build.h"
36 #include "document.h"
37 #include "callbacks.h"
38 #include "filetypes.h"
39 #include "keybindings.h"
40 #include "main.h"
41 #include "navqueue.h"
42 #include "prefs.h"
43 #include "support.h"
44 #include "ui_utils.h"
45 #include "utils.h"
46 #include "vte.h"
48 #include <string.h>
49 #include <stdlib.h>
50 #include <time.h>
52 #include <gdk/gdkkeysyms.h>
55 /* used for parse_file_line */
56 typedef struct
58 const gchar *string; /* line data */
59 const gchar *pattern; /* pattern to split the error message into some fields */
60 guint min_fields; /* used to detect errors after parsing */
61 guint line_idx; /* idx of the field where the line is */
62 gint file_idx; /* idx of the field where the filename is or -1 */
64 ParseData;
66 MessageWindow msgwindow;
68 enum
70 MSG_COL_LINE = 0,
71 MSG_COL_DOC_ID,
72 MSG_COL_COLOR,
73 MSG_COL_STRING,
74 MSG_COL_COUNT
77 enum
79 COMPILER_COL_COLOR = 0,
80 COMPILER_COL_STRING,
81 COMPILER_COL_COUNT
85 static GdkColor color_error = {0, 0xFFFF, 0, 0};
86 static GdkColor color_context = {0, 0x7FFF, 0, 0};
87 static GdkColor color_message = {0, 0, 0, 0xD000};
90 static void prepare_msg_tree_view(void);
91 static void prepare_status_tree_view(void);
92 static void prepare_compiler_tree_view(void);
93 static GtkWidget *create_message_popup_menu(gint type);
94 static gboolean on_msgwin_button_press_event(GtkWidget *widget, GdkEventButton *event,
95 gpointer user_data);
96 static void on_scribble_populate(GtkTextView *textview, GtkMenu *arg1, gpointer user_data);
99 void msgwin_show_hide_tabs(void)
101 ui_widget_show_hide(gtk_widget_get_parent(msgwindow.tree_status), interface_prefs.msgwin_status_visible);
102 ui_widget_show_hide(gtk_widget_get_parent(msgwindow.tree_compiler), interface_prefs.msgwin_compiler_visible);
103 ui_widget_show_hide(gtk_widget_get_parent(msgwindow.tree_msg), interface_prefs.msgwin_messages_visible);
104 ui_widget_show_hide(gtk_widget_get_parent(msgwindow.scribble), interface_prefs.msgwin_scribble_visible);
109 * Sets the Messages path for opening any parsed filenames without absolute path from message lines.
111 * @param messages_dir The directory.
113 GEANY_API_SYMBOL
114 void msgwin_set_messages_dir(const gchar *messages_dir)
116 g_free(msgwindow.messages_dir);
117 msgwindow.messages_dir = g_strdup(messages_dir);
121 static void load_color(const gchar *color_name, GdkColor *color)
123 GdkRGBA rgba_color;
124 GtkWidgetPath *path = gtk_widget_path_new();
125 GtkStyleContext *ctx = gtk_style_context_new();
127 gtk_widget_path_append_type(path, GTK_TYPE_WINDOW);
128 gtk_widget_path_iter_set_name(path, -1, color_name);
129 gtk_style_context_set_screen(ctx, gdk_screen_get_default());
130 gtk_style_context_set_path(ctx, path);
131 gtk_style_context_get_color(ctx, gtk_style_context_get_state(ctx), &rgba_color);
133 color->red = 0xffff * rgba_color.red;
134 color->green = 0xffff * rgba_color.green;
135 color->blue = 0xffff * rgba_color.blue;
137 gtk_widget_path_unref(path);
138 g_object_unref(ctx);
142 void msgwin_init(void)
144 msgwindow.notebook = ui_lookup_widget(main_widgets.window, "notebook_info");
145 msgwindow.tree_status = ui_lookup_widget(main_widgets.window, "treeview3");
146 msgwindow.tree_msg = ui_lookup_widget(main_widgets.window, "treeview4");
147 msgwindow.tree_compiler = ui_lookup_widget(main_widgets.window, "treeview5");
148 msgwindow.scribble = ui_lookup_widget(main_widgets.window, "textview_scribble");
149 msgwindow.messages_dir = NULL;
151 prepare_status_tree_view();
152 prepare_msg_tree_view();
153 prepare_compiler_tree_view();
154 msgwindow.popup_status_menu = create_message_popup_menu(MSG_STATUS);
155 msgwindow.popup_msg_menu = create_message_popup_menu(MSG_MESSAGE);
156 msgwindow.popup_compiler_menu = create_message_popup_menu(MSG_COMPILER);
158 ui_widget_modify_font_from_string(msgwindow.scribble, interface_prefs.msgwin_font);
159 g_signal_connect(msgwindow.scribble, "populate-popup", G_CALLBACK(on_scribble_populate), NULL);
161 load_color("geany-compiler-error", &color_error);
162 load_color("geany-compiler-context", &color_context);
163 load_color("geany-compiler-message", &color_message);
167 void msgwin_finalize(void)
169 g_free(msgwindow.messages_dir);
173 static gboolean on_msgwin_key_press_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
175 gboolean enter_or_return = ui_is_keyval_enter_or_return(event->keyval);
177 if (enter_or_return || event->keyval == GDK_KEY_space)
179 switch (GPOINTER_TO_INT(data))
181 case MSG_COMPILER:
182 { /* key press in the compiler treeview */
183 msgwin_goto_compiler_file_line(enter_or_return);
184 break;
186 case MSG_MESSAGE:
187 { /* key press in the message treeview (results of 'Find usage') */
188 msgwin_goto_messages_file_line(enter_or_return);
189 break;
193 return FALSE;
197 /* does some preparing things to the status message list widget */
198 static void prepare_status_tree_view(void)
200 GtkCellRenderer *renderer;
201 GtkTreeViewColumn *column;
203 msgwindow.store_status = gtk_list_store_new(1, G_TYPE_STRING);
204 gtk_tree_view_set_model(GTK_TREE_VIEW(msgwindow.tree_status), GTK_TREE_MODEL(msgwindow.store_status));
205 g_object_unref(msgwindow.store_status);
207 renderer = gtk_cell_renderer_text_new();
208 column = gtk_tree_view_column_new_with_attributes(_("Status messages"), renderer, "text", 0, NULL);
209 gtk_tree_view_append_column(GTK_TREE_VIEW(msgwindow.tree_status), column);
211 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(msgwindow.tree_status), FALSE);
213 ui_widget_modify_font_from_string(msgwindow.tree_status, interface_prefs.msgwin_font);
215 g_signal_connect(msgwindow.tree_status, "button-press-event",
216 G_CALLBACK(on_msgwin_button_press_event), GINT_TO_POINTER(MSG_STATUS));
220 /* does some preparing things to the message list widget
221 * (currently used for showing results of 'Find usage') */
222 static void prepare_msg_tree_view(void)
224 GtkCellRenderer *renderer;
225 GtkTreeViewColumn *column;
226 GtkTreeSelection *selection;
228 /* line, doc id, fg, str */
229 msgwindow.store_msg = gtk_list_store_new(MSG_COL_COUNT, G_TYPE_INT, G_TYPE_UINT,
230 GDK_TYPE_COLOR, G_TYPE_STRING);
231 gtk_tree_view_set_model(GTK_TREE_VIEW(msgwindow.tree_msg), GTK_TREE_MODEL(msgwindow.store_msg));
232 g_object_unref(msgwindow.store_msg);
234 renderer = gtk_cell_renderer_text_new();
235 column = gtk_tree_view_column_new_with_attributes(NULL, renderer,
236 "foreground-gdk", MSG_COL_COLOR, "text", MSG_COL_STRING, NULL);
237 gtk_tree_view_append_column(GTK_TREE_VIEW(msgwindow.tree_msg), column);
239 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(msgwindow.tree_msg), FALSE);
241 ui_widget_modify_font_from_string(msgwindow.tree_msg, interface_prefs.msgwin_font);
243 /* use button-release-event so the selection has changed
244 * (connect_after button-press-event doesn't work) */
245 g_signal_connect(msgwindow.tree_msg, "button-release-event",
246 G_CALLBACK(on_msgwin_button_press_event), GINT_TO_POINTER(MSG_MESSAGE));
247 /* for double-clicking only, after the first release */
248 g_signal_connect(msgwindow.tree_msg, "button-press-event",
249 G_CALLBACK(on_msgwin_button_press_event), GINT_TO_POINTER(MSG_MESSAGE));
250 g_signal_connect(msgwindow.tree_msg, "key-press-event",
251 G_CALLBACK(on_msgwin_key_press_event), GINT_TO_POINTER(MSG_MESSAGE));
253 /* selection handling */
254 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(msgwindow.tree_msg));
255 gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
256 /*g_signal_connect(selection, "changed",G_CALLBACK(on_msg_tree_selection_changed), NULL);*/
260 /* does some preparing things to the compiler list widget */
261 static void prepare_compiler_tree_view(void)
263 GtkCellRenderer *renderer;
264 GtkTreeViewColumn *column;
265 GtkTreeSelection *selection;
267 msgwindow.store_compiler = gtk_list_store_new(COMPILER_COL_COUNT, GDK_TYPE_COLOR, G_TYPE_STRING);
268 gtk_tree_view_set_model(GTK_TREE_VIEW(msgwindow.tree_compiler), GTK_TREE_MODEL(msgwindow.store_compiler));
269 g_object_unref(msgwindow.store_compiler);
271 renderer = gtk_cell_renderer_text_new();
272 column = gtk_tree_view_column_new_with_attributes(NULL, renderer,
273 "foreground-gdk", COMPILER_COL_COLOR, "text", COMPILER_COL_STRING, NULL);
274 gtk_tree_view_append_column(GTK_TREE_VIEW(msgwindow.tree_compiler), column);
276 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(msgwindow.tree_compiler), FALSE);
278 ui_widget_modify_font_from_string(msgwindow.tree_compiler, interface_prefs.msgwin_font);
280 /* use button-release-event so the selection has changed
281 * (connect_after button-press-event doesn't work) */
282 g_signal_connect(msgwindow.tree_compiler, "button-release-event",
283 G_CALLBACK(on_msgwin_button_press_event), GINT_TO_POINTER(MSG_COMPILER));
284 /* for double-clicking only, after the first release */
285 g_signal_connect(msgwindow.tree_compiler, "button-press-event",
286 G_CALLBACK(on_msgwin_button_press_event), GINT_TO_POINTER(MSG_COMPILER));
287 g_signal_connect(msgwindow.tree_compiler, "key-press-event",
288 G_CALLBACK(on_msgwin_key_press_event), GINT_TO_POINTER(MSG_COMPILER));
290 /* selection handling */
291 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(msgwindow.tree_compiler));
292 gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
293 /*g_signal_connect(selection, "changed", G_CALLBACK(on_msg_tree_selection_changed), NULL);*/
296 static const GdkColor *get_color(gint msg_color)
298 switch (msg_color)
300 case COLOR_RED: return &color_error;
301 case COLOR_DARK_RED: return &color_context;
302 case COLOR_BLUE: return &color_message;
303 default: return NULL;
309 * Adds a formatted message in the compiler tab treeview in the messages window.
311 * @param msg_color A color to be used for the text. It must be an element of #MsgColors.
312 * @param format @c printf()-style format string.
313 * @param ... Arguments for the @c format string.
315 * @see msgwin_compiler_add_string()
317 * @since 0.16
319 GEANY_API_SYMBOL
320 void msgwin_compiler_add(gint msg_color, const gchar *format, ...)
322 gchar *string;
323 va_list args;
325 va_start(args, format);
326 string = g_strdup_vprintf(format, args);
327 va_end(args);
328 msgwin_compiler_add_string(msg_color, string);
329 g_free(string);
333 * Adds a new message in the compiler tab treeview in the messages window.
335 * @param msg_color A color to be used for the text. It must be an element of #MsgColors.
336 * @param msg Compiler message to be added.
338 * @see msgwin_compiler_add()
340 * @since 1.34 (API 236)
342 GEANY_API_SYMBOL
343 void msgwin_compiler_add_string(gint msg_color, const gchar *msg)
345 GtkTreeIter iter;
346 const GdkColor *color = get_color(msg_color);
347 gchar *utf8_msg;
349 if (! g_utf8_validate(msg, -1, NULL))
350 utf8_msg = utils_get_utf8_from_locale(msg);
351 else
352 utf8_msg = (gchar *) msg;
354 gtk_list_store_append(msgwindow.store_compiler, &iter);
355 gtk_list_store_set(msgwindow.store_compiler, &iter,
356 COMPILER_COL_COLOR, color, COMPILER_COL_STRING, utf8_msg, -1);
358 if (ui_prefs.msgwindow_visible && interface_prefs.compiler_tab_autoscroll)
360 GtkTreePath *path = gtk_tree_model_get_path(
361 gtk_tree_view_get_model(GTK_TREE_VIEW(msgwindow.tree_compiler)), &iter);
363 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(msgwindow.tree_compiler), path, NULL, TRUE, 0.5, 0.5);
364 gtk_tree_path_free(path);
367 if (utf8_msg != msg)
368 g_free(utf8_msg);
372 void msgwin_show_hide(gboolean show)
374 ui_prefs.msgwindow_visible = show;
375 ignore_callback = TRUE;
376 gtk_check_menu_item_set_active(
377 GTK_CHECK_MENU_ITEM(ui_lookup_widget(main_widgets.window, "menu_show_messages_window1")),
378 show);
379 ignore_callback = FALSE;
380 ui_widget_show_hide(main_widgets.message_window_notebook, show);
381 /* set the input focus back to the editor */
382 keybindings_send_command(GEANY_KEY_GROUP_FOCUS, GEANY_KEYS_FOCUS_EDITOR);
387 * Adds a formatted message in the messages tab treeview in the messages window.
389 * If @a line and @a doc are set, clicking on this line jumps into the file
390 * which is specified by @a doc into the line specified with @a line.
392 * @param msg_color A color to be used for the text. It must be an element of #MsgColors.
393 * @param line The document's line where the message belongs to. Set to @c -1 to ignore.
394 * @param doc @nullable The document. Set to @c NULL to ignore.
395 * @param format @c printf()-style format string.
396 * @param ... Arguments for the @c format string.
398 * @see msgwin_msg_add_string()
400 * @since 0.16
402 GEANY_API_SYMBOL
403 void msgwin_msg_add(gint msg_color, gint line, GeanyDocument *doc, const gchar *format, ...)
405 gchar *string;
406 va_list args;
408 va_start(args, format);
409 string = g_strdup_vprintf(format, args);
410 va_end(args);
412 msgwin_msg_add_string(msg_color, line, doc, string);
413 g_free(string);
418 * Adds a new message in the messages tab treeview in the messages window.
420 * If @a line and @a doc are set, clicking on this line jumps into the
421 * file which is specified by @a doc into the line specified with @a line.
423 * @param msg_color A color to be used for the text. It must be an element of #MsgColors.
424 * @param line The document's line where the message belongs to. Set to @c -1 to ignore.
425 * @param doc @nullable The document. Set to @c NULL to ignore.
426 * @param string Message to be added.
428 * @see msgwin_msg_add()
430 * @since 1.34 (API 236)
432 GEANY_API_SYMBOL
433 void msgwin_msg_add_string(gint msg_color, gint line, GeanyDocument *doc, const gchar *string)
435 GtkTreeIter iter;
436 const GdkColor *color = get_color(msg_color);
437 gchar *tmp;
438 gsize len;
439 gchar *utf8_msg;
441 if (! ui_prefs.msgwindow_visible)
442 msgwin_show_hide(TRUE);
444 /* work around a strange problem when adding very long lines(greater than 4000 bytes)
445 * cut the string to a maximum of 1024 bytes and discard the rest */
446 /* TODO: find the real cause for the display problem / if it is GtkTreeView file a bug report */
447 len = strlen(string);
448 if (len > 1024)
449 tmp = g_strndup(string, 1024);
450 else
451 tmp = g_strdup(string);
453 if (! g_utf8_validate(tmp, -1, NULL))
454 utf8_msg = utils_get_utf8_from_locale(tmp);
455 else
456 utf8_msg = tmp;
458 gtk_list_store_append(msgwindow.store_msg, &iter);
459 gtk_list_store_set(msgwindow.store_msg, &iter,
460 MSG_COL_LINE, line, MSG_COL_DOC_ID, doc ? doc->id : 0, MSG_COL_COLOR,
461 color, MSG_COL_STRING, utf8_msg, -1);
463 g_free(tmp);
464 if (utf8_msg != tmp)
465 g_free(utf8_msg);
470 * Logs a new status message *without* setting the status bar.
472 * Use @ref ui_set_statusbar() to display text on the statusbar.
474 * @param string Status message to be logged.
476 * @see msgwin_status_add()
478 * @since 1.34 (API 236)
480 GEANY_API_SYMBOL
481 void msgwin_status_add_string(const gchar *string)
483 GtkTreeIter iter;
484 gchar *statusmsg, *time_str;
486 /* add a timestamp to status messages */
487 time_str = utils_get_current_time_string(FALSE);
488 statusmsg = g_strconcat(time_str, ": ", string, NULL);
489 g_free(time_str);
491 /* add message to Status window */
492 gtk_list_store_append(msgwindow.store_status, &iter);
493 gtk_list_store_set(msgwindow.store_status, &iter, 0, statusmsg, -1);
494 g_free(statusmsg);
496 if (G_LIKELY(main_status.main_window_realized))
498 GtkTreePath *path = gtk_tree_model_get_path(gtk_tree_view_get_model(GTK_TREE_VIEW(msgwindow.tree_status)), &iter);
500 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(msgwindow.tree_status), path, NULL, FALSE, 0.0, 0.0);
501 if (prefs.switch_to_status)
502 gtk_notebook_set_current_page(GTK_NOTEBOOK(msgwindow.notebook), MSG_STATUS);
503 gtk_tree_path_free(path);
508 * Logs a formatted status message *without* setting the status bar.
510 * Use @ref ui_set_statusbar() to display text on the statusbar.
512 * @param format @c printf()-style format string.
513 * @param ... Arguments for the @c format string.
515 * @see msgwin_status_add_string()
517 * @since 0.12
519 GEANY_API_SYMBOL
520 void msgwin_status_add(const gchar *format, ...)
522 gchar *string;
523 va_list args;
525 va_start(args, format);
526 string = g_strdup_vprintf(format, args);
527 va_end(args);
529 msgwin_status_add_string(string);
530 g_free(string);
534 static void
535 on_message_treeview_clear_activate(GtkMenuItem *menuitem, gpointer user_data)
537 gint tabnum = GPOINTER_TO_INT(user_data);
539 msgwin_clear_tab(tabnum);
543 static void
544 on_compiler_treeview_copy_activate(GtkMenuItem *menuitem, gpointer user_data)
546 GtkWidget *tv = NULL;
547 GtkTreeSelection *selection;
548 GtkTreeModel *model;
549 GtkTreeIter iter;
550 gint str_idx = COMPILER_COL_STRING;
552 switch (GPOINTER_TO_INT(user_data))
554 case MSG_STATUS:
555 tv = msgwindow.tree_status;
556 str_idx = 0;
557 break;
559 case MSG_COMPILER:
560 tv = msgwindow.tree_compiler;
561 break;
563 case MSG_MESSAGE:
564 tv = msgwindow.tree_msg;
565 str_idx = MSG_COL_STRING;
566 break;
568 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv));
570 if (gtk_tree_selection_get_selected(selection, &model, &iter))
572 gchar *string;
574 gtk_tree_model_get(model, &iter, str_idx, &string, -1);
575 if (!EMPTY(string))
577 gtk_clipboard_set_text(gtk_clipboard_get(gdk_atom_intern("CLIPBOARD", FALSE)),
578 string, -1);
580 g_free(string);
585 static void on_compiler_treeview_copy_all_activate(GtkMenuItem *menuitem, gpointer user_data)
587 GtkListStore *store = msgwindow.store_compiler;
588 GtkTreeIter iter;
589 GString *str = g_string_new("");
590 gint str_idx = COMPILER_COL_STRING;
591 gboolean valid;
593 switch (GPOINTER_TO_INT(user_data))
595 case MSG_STATUS:
596 store = msgwindow.store_status;
597 str_idx = 0;
598 break;
600 case MSG_COMPILER:
601 /* default values */
602 break;
604 case MSG_MESSAGE:
605 store = msgwindow.store_msg;
606 str_idx = MSG_COL_STRING;
607 break;
610 /* walk through the list and copy every line into a string */
611 valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter);
612 while (valid)
614 gchar *line;
616 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, str_idx, &line, -1);
617 if (!EMPTY(line))
619 g_string_append(str, line);
620 g_string_append_c(str, '\n');
622 g_free(line);
624 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
627 /* copy the string into the clipboard */
628 if (str->len > 0)
630 gtk_clipboard_set_text(
631 gtk_clipboard_get(gdk_atom_intern("CLIPBOARD", FALSE)),
632 str->str,
633 str->len);
635 g_string_free(str, TRUE);
639 static void
640 on_hide_message_window(GtkMenuItem *menuitem, gpointer user_data)
642 msgwin_show_hide(FALSE);
646 static GtkWidget *create_message_popup_menu(gint type)
648 GtkWidget *message_popup_menu, *clear, *copy, *copy_all, *image;
650 message_popup_menu = gtk_menu_new();
652 clear = gtk_image_menu_item_new_from_stock(GTK_STOCK_CLEAR, NULL);
653 gtk_widget_show(clear);
654 gtk_container_add(GTK_CONTAINER(message_popup_menu), clear);
655 g_signal_connect(clear, "activate",
656 G_CALLBACK(on_message_treeview_clear_activate), GINT_TO_POINTER(type));
658 copy = gtk_image_menu_item_new_with_mnemonic(_("C_opy"));
659 gtk_widget_show(copy);
660 gtk_container_add(GTK_CONTAINER(message_popup_menu), copy);
661 image = gtk_image_new_from_stock(GTK_STOCK_COPY, GTK_ICON_SIZE_MENU);
662 gtk_widget_show(image);
663 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(copy), image);
664 g_signal_connect(copy, "activate",
665 G_CALLBACK(on_compiler_treeview_copy_activate), GINT_TO_POINTER(type));
667 copy_all = gtk_image_menu_item_new_with_mnemonic(_("Copy _All"));
668 gtk_widget_show(copy_all);
669 gtk_container_add(GTK_CONTAINER(message_popup_menu), copy_all);
670 image = gtk_image_new_from_stock(GTK_STOCK_COPY, GTK_ICON_SIZE_MENU);
671 gtk_widget_show(image);
672 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(copy_all), image);
673 g_signal_connect(copy_all, "activate",
674 G_CALLBACK(on_compiler_treeview_copy_all_activate), GINT_TO_POINTER(type));
676 msgwin_menu_add_common_items(GTK_MENU(message_popup_menu));
678 return message_popup_menu;
682 static void on_scribble_populate(GtkTextView *textview, GtkMenu *arg1, gpointer user_data)
684 msgwin_menu_add_common_items(arg1);
688 /* Menu items that should be on all message window popup menus */
689 void msgwin_menu_add_common_items(GtkMenu *menu)
691 GtkWidget *item;
693 item = gtk_separator_menu_item_new();
694 gtk_widget_show(item);
695 gtk_container_add(GTK_CONTAINER(menu), item);
697 item = gtk_menu_item_new_with_mnemonic(_("_Hide Message Window"));
698 gtk_widget_show(item);
699 gtk_container_add(GTK_CONTAINER(menu), item);
700 g_signal_connect(item, "activate", G_CALLBACK(on_hide_message_window), NULL);
704 /* look back up from the current path and find the directory we came from */
705 static gboolean
706 find_prev_build_dir(GtkTreePath *cur, GtkTreeModel *model, gchar **prefix)
708 GtkTreeIter iter;
709 *prefix = NULL;
711 while (gtk_tree_path_prev(cur))
713 if (gtk_tree_model_get_iter(model, &iter, cur))
715 gchar *string;
716 gtk_tree_model_get(model, &iter, COMPILER_COL_STRING, &string, -1);
717 if (string != NULL && build_parse_make_dir(string, prefix))
719 g_free(string);
720 return TRUE;
722 g_free(string);
726 return FALSE;
730 static gboolean goto_compiler_file_line(const gchar *fname, gint line, gboolean focus_editor)
732 gboolean ret = FALSE;
733 gchar *filename;
735 if (!fname || line <= -1)
736 return FALSE;
738 filename = utils_get_locale_from_utf8(fname);
740 /* If the path doesn't exist, try the current document.
741 * This happens when we receive build messages in the wrong order - after the
742 * 'Leaving directory' messages */
743 if (!g_file_test(filename, G_FILE_TEST_EXISTS))
745 gchar *cur_dir = utils_get_current_file_dir_utf8();
746 gchar *name;
748 if (cur_dir)
750 /* we let the user know we couldn't find the parsed filename from the message window */
751 SETPTR(cur_dir, utils_get_locale_from_utf8(cur_dir));
752 name = g_path_get_basename(filename);
753 SETPTR(name, g_build_path(G_DIR_SEPARATOR_S, cur_dir, name, NULL));
754 g_free(cur_dir);
756 if (g_file_test(name, G_FILE_TEST_EXISTS))
758 ui_set_statusbar(FALSE, _("Could not find file '%s' - trying the current document path."),
759 fname);
760 SETPTR(filename, name);
762 else
763 g_free(name);
768 gchar *utf8_filename = utils_get_utf8_from_locale(filename);
769 GeanyDocument *doc = document_find_by_filename(utf8_filename);
770 GeanyDocument *old_doc = document_get_current();
772 g_free(utf8_filename);
774 if (doc == NULL) /* file not already open */
775 doc = document_open_file(filename, FALSE, NULL, NULL);
777 if (doc != NULL)
779 if (! doc->changed && editor_prefs.use_indicators) /* if modified, line may be wrong */
780 editor_indicator_set_on_line(doc->editor, GEANY_INDICATOR_ERROR, line - 1);
782 ret = navqueue_goto_line(old_doc, doc, line);
783 if (ret && focus_editor)
784 gtk_widget_grab_focus(GTK_WIDGET(doc->editor->sci));
786 ret = TRUE;
790 g_free(filename);
792 return ret;
796 gboolean msgwin_goto_compiler_file_line(gboolean focus_editor)
798 GtkTreeIter iter;
799 GtkTreeModel *model;
800 GtkTreeSelection *selection;
801 gchar *string;
802 GdkColor *color;
804 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(msgwindow.tree_compiler));
805 if (gtk_tree_selection_get_selected(selection, &model, &iter))
807 /* if the item is not coloured red, it's not an error line */
808 gtk_tree_model_get(model, &iter, COMPILER_COL_COLOR, &color, -1);
809 if (color == NULL || ! gdk_color_equal(color, &color_error))
811 if (color != NULL)
812 gdk_color_free(color);
813 return FALSE;
815 gdk_color_free(color);
817 gtk_tree_model_get(model, &iter, COMPILER_COL_STRING, &string, -1);
818 if (string != NULL)
820 gint line;
821 gchar *filename, *dir;
822 GtkTreePath *path;
823 gboolean ret;
825 path = gtk_tree_model_get_path(model, &iter);
826 find_prev_build_dir(path, model, &dir);
827 gtk_tree_path_free(path);
828 msgwin_parse_compiler_error_line(string, dir, &filename, &line);
829 g_free(string);
830 g_free(dir);
832 ret = goto_compiler_file_line(filename, line, focus_editor);
833 g_free(filename);
834 return ret;
837 return FALSE;
841 static void make_absolute(gchar **filename, const gchar *dir)
843 guint skip_dot_slash = 0; /* number of characters to skip at the beginning of the filename */
845 if (*filename == NULL)
846 return;
848 /* skip some characters at the beginning of the filename, at the moment only "./"
849 * can be extended if other "trash" is known */
850 if (strncmp(*filename, "./", 2) == 0)
851 skip_dot_slash = 2;
853 /* add directory */
854 if (! utils_is_absolute_path(*filename))
855 SETPTR(*filename, g_build_filename(dir, *filename + skip_dot_slash, NULL));
859 /* try to parse the file and line number where the error occurred described in line
860 * and when something useful is found, it stores the line number in *line and the
861 * relevant file with the error in *filename.
862 * *line will be -1 if no error was found in string.
863 * *filename must be freed unless it is NULL. */
864 static void parse_file_line(ParseData *data, gchar **filename, gint *line)
866 gchar *end = NULL;
867 gchar **fields;
869 *filename = NULL;
870 *line = -1;
872 g_return_if_fail(data->string != NULL);
874 fields = g_strsplit_set(data->string, data->pattern, data->min_fields);
876 /* parse the line */
877 if (g_strv_length(fields) < data->min_fields)
879 g_strfreev(fields);
880 return;
883 *line = strtol(fields[data->line_idx], &end, 10);
885 /* if the line could not be read, line is 0 and an error occurred, so we leave */
886 if (fields[data->line_idx] == end)
888 g_strfreev(fields);
889 return;
892 /* let's stop here if there is no filename in the error message */
893 if (data->file_idx == -1)
895 /* we have no filename in the error message, so take the current one and hope it's correct */
896 GeanyDocument *doc = document_get_current();
897 if (doc != NULL)
898 *filename = g_strdup(doc->file_name);
899 g_strfreev(fields);
900 return;
903 *filename = g_strdup(fields[data->file_idx]);
904 g_strfreev(fields);
908 static void parse_compiler_error_line(const gchar *string,
909 gchar **filename, gint *line)
911 ParseData data = {NULL, NULL, 0, 0, 0};
913 data.string = string;
915 switch (build_info.file_type_id)
917 case GEANY_FILETYPES_PHP:
919 /* Parse error: parse error, unexpected T_CASE in brace_bug.php on line 3
920 * Parse error: syntax error, unexpected T_LNUMBER, expecting T_FUNCTION in bob.php on line 16 */
921 gchar *tmp = strstr(string, " in ");
923 if (tmp != NULL)
925 data.string = tmp;
926 data.pattern = " ";
927 data.min_fields = 6;
928 data.line_idx = 5;
929 data.file_idx = 2;
931 else
933 data.pattern = " ";
934 data.min_fields = 11;
935 data.line_idx = 10;
936 data.file_idx = 7;
938 break;
940 case GEANY_FILETYPES_PERL:
942 /* syntax error at test.pl line 7, near "{ */
943 data.pattern = " ";
944 data.min_fields = 6;
945 data.line_idx = 5;
946 data.file_idx = 3;
947 break;
949 /* the error output of python and tcl equals */
950 case GEANY_FILETYPES_TCL:
951 case GEANY_FILETYPES_PYTHON:
953 /* File "HyperArch.py", line 37, in ?
954 * (file "clrdial.tcl" line 12)
955 * */
956 if (strstr(string, " line ") != NULL)
958 /* Tcl and old Python format (<= Python 2.5) */
959 data.pattern = " \"";
960 data.min_fields = 6;
961 data.line_idx = 5;
962 data.file_idx = 2;
964 else
966 /* SyntaxError: ('invalid syntax', ('sender.py', 149, 20, ' ...'))
967 * (used since Python 2.6) */
968 data.pattern = ",'";
969 data.min_fields = 8;
970 data.line_idx = 6;
971 data.file_idx = 4;
973 break;
975 case GEANY_FILETYPES_BASIC:
976 case GEANY_FILETYPES_PASCAL:
977 case GEANY_FILETYPES_CS:
979 /* getdrive.bas(52) error 18: Syntax error in '? GetAllDrives'
980 * bandit.pas(149,3) Fatal: Syntax error, ";" expected but "ELSE" found */
981 data.pattern = "(";
982 data.min_fields = 2;
983 data.line_idx = 1;
984 data.file_idx = 0;
985 break;
987 case GEANY_FILETYPES_D:
989 /* GNU D compiler front-end, gdc
990 * gantry.d:18: variable gantry.main.c reference to auto class must be auto
991 * warning - gantry.d:20: statement is not reachable
992 * Digital Mars dmd compiler
993 * warning - pi.d(118): implicit conversion of expression (digit) of type int ...
994 * gantry.d(18): variable gantry.main.c reference to auto class must be auto */
995 if (strncmp(string, "warning - ", 10) == 0)
997 data.pattern = " (:";
998 data.min_fields = 4;
999 data.line_idx = 3;
1000 data.file_idx = 2;
1002 else
1004 data.pattern = "(:";
1005 data.min_fields = 2;
1006 data.line_idx = 1;
1007 data.file_idx = 0;
1009 break;
1011 case GEANY_FILETYPES_HTML:
1013 /* line 78 column 7 - Warning: <table> missing '>' for end of tag */
1014 data.pattern = " ";
1015 data.min_fields = 4;
1016 data.line_idx = 1;
1017 data.file_idx = -1;
1018 break;
1020 /* All GNU gcc-like error messages */
1021 case GEANY_FILETYPES_C:
1022 case GEANY_FILETYPES_CPP:
1023 case GEANY_FILETYPES_RUBY:
1024 case GEANY_FILETYPES_JAVA:
1025 /* only gcc is supported, I don't know any other C(++) compilers and their error messages
1026 * empty.h:4: Warnung: type defaults to `int' in declaration of `foo'
1027 * empty.c:21: error: conflicting types for `foo'
1028 * Only parse file and line, so that linker errors will also work (with -g) */
1029 case GEANY_FILETYPES_F77:
1030 case GEANY_FILETYPES_FORTRAN:
1031 case GEANY_FILETYPES_LATEX:
1032 /* ./kommtechnik_2b.tex:18: Emergency stop. */
1033 case GEANY_FILETYPES_MAKE: /* Assume makefile is building with gcc */
1034 case GEANY_FILETYPES_NONE:
1035 default: /* The default is a GNU gcc type error */
1037 if (build_info.file_type_id == GEANY_FILETYPES_JAVA &&
1038 strncmp(string, "[javac]", 7) == 0)
1040 /* Java Apache Ant.
1041 * [javac] <Full Path to File + extension>:<line n°>: <error> */
1042 data.pattern = " :";
1043 data.min_fields = 4;
1044 data.line_idx = 2;
1045 data.file_idx = 1;
1046 break;
1048 /* don't accidentally find libtool versions x:y:x and think it is a file name */
1049 if (strstr(string, "libtool --mode=link") == NULL)
1051 data.pattern = ":";
1052 data.min_fields = 3;
1053 data.line_idx = 1;
1054 data.file_idx = 0;
1055 break;
1060 if (data.pattern != NULL)
1061 parse_file_line(&data, filename, line);
1065 /* try to parse the file and line number where the error occurred described in string
1066 * and when something useful is found, it stores the line number in *line and the
1067 * relevant file with the error in *filename.
1068 * *line will be -1 if no error was found in string.
1069 * *filename must be freed unless it is NULL. */
1070 void msgwin_parse_compiler_error_line(const gchar *string, const gchar *dir,
1071 gchar **filename, gint *line)
1073 GeanyFiletype *ft;
1074 gchar *trimmed_string, *utf8_dir;
1076 *filename = NULL;
1077 *line = -1;
1079 if (G_UNLIKELY(string == NULL))
1080 return;
1082 if (dir == NULL)
1083 utf8_dir = utils_get_utf8_from_locale(build_info.dir);
1084 else
1085 utf8_dir = g_strdup(dir);
1086 g_return_if_fail(utf8_dir != NULL);
1088 trimmed_string = g_strdup(string);
1089 g_strchug(trimmed_string); /* remove possible leading whitespace */
1091 ft = filetypes[build_info.file_type_id];
1093 /* try parsing with a custom regex */
1094 if (!filetypes_parse_error_message(ft, trimmed_string, filename, line))
1096 /* fallback to default old-style parsing */
1097 parse_compiler_error_line(trimmed_string, filename, line);
1099 make_absolute(filename, utf8_dir);
1100 g_free(trimmed_string);
1101 g_free(utf8_dir);
1105 /* Tries to parse strings of the file:line style, allowing line field to be missing
1106 * * filename is filled with the filename, should be freed
1107 * * line is filled with the line number or -1 */
1108 static void msgwin_parse_generic_line(const gchar *string, gchar **filename, gint *line)
1110 gchar **fields;
1111 gboolean incertain = TRUE; /* whether we're reasonably certain of the result */
1113 *filename = NULL;
1114 *line = -1;
1116 fields = g_strsplit(string, ":", 2);
1117 /* extract the filename */
1118 if (fields[0] != NULL)
1120 *filename = utils_get_locale_from_utf8(fields[0]);
1121 if (msgwindow.messages_dir != NULL)
1122 make_absolute(filename, msgwindow.messages_dir);
1124 /* now the line */
1125 if (fields[1] != NULL)
1127 gchar *end;
1129 *line = strtol(fields[1], &end, 10);
1130 if (end == fields[1])
1131 *line = -1;
1132 else if (*end == ':' || g_ascii_isspace(*end))
1133 { /* if we have a blank or a separator right after the number, assume we really got a
1134 * filename (it's a grep-like syntax) */
1135 incertain = FALSE;
1139 /* if we aren't sure we got a supposedly correct filename, check it */
1140 if (incertain && ! g_file_test(*filename, G_FILE_TEST_EXISTS))
1142 SETPTR(*filename, NULL);
1143 *line = -1;
1146 g_strfreev(fields);
1150 gboolean msgwin_goto_messages_file_line(gboolean focus_editor)
1152 GtkTreeIter iter;
1153 GtkTreeModel *model;
1154 GtkTreeSelection *selection;
1155 gboolean ret = FALSE;
1157 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(msgwindow.tree_msg));
1158 if (gtk_tree_selection_get_selected(selection, &model, &iter))
1160 gint line;
1161 guint id;
1162 gchar *string;
1163 GeanyDocument *doc;
1164 GeanyDocument *old_doc = document_get_current();
1166 gtk_tree_model_get(model, &iter,
1167 MSG_COL_LINE, &line, MSG_COL_DOC_ID, &id, MSG_COL_STRING, &string, -1);
1168 if (line >= 0 && id > 0)
1170 /* check doc is still open */
1171 doc = document_find_by_id(id);
1172 if (!doc)
1174 ui_set_statusbar(FALSE, _("The document has been closed."));
1175 utils_beep();
1177 else
1179 ret = navqueue_goto_line(old_doc, doc, line);
1180 if (ret && focus_editor)
1181 gtk_widget_grab_focus(GTK_WIDGET(doc->editor->sci));
1184 else if (line < 0 && string != NULL)
1186 gchar *filename;
1188 /* try with a file:line parsing */
1189 msgwin_parse_generic_line(string, &filename, &line);
1190 if (filename != NULL)
1192 /* use document_open_file to find an already open file, or open it in place */
1193 doc = document_open_file(filename, FALSE, NULL, NULL);
1194 if (doc != NULL)
1196 ret = (line < 0) ? TRUE : navqueue_goto_line(old_doc, doc, line);
1197 if (ret && focus_editor)
1198 gtk_widget_grab_focus(GTK_WIDGET(doc->editor->sci));
1201 g_free(filename);
1203 g_free(string);
1205 return ret;
1209 static gboolean on_msgwin_button_press_event(GtkWidget *widget, GdkEventButton *event,
1210 gpointer user_data)
1212 /* user_data might be NULL, GPOINTER_TO_INT returns 0 if called with NULL */
1213 gboolean double_click = event->type == GDK_2BUTTON_PRESS;
1215 if (event->button == 1 && (event->type == GDK_BUTTON_RELEASE || double_click))
1217 switch (GPOINTER_TO_INT(user_data))
1219 case MSG_COMPILER:
1220 { /* mouse click in the compiler treeview */
1221 msgwin_goto_compiler_file_line(double_click);
1222 break;
1224 case MSG_MESSAGE:
1225 { /* mouse click in the message treeview (results of 'Find usage') */
1226 msgwin_goto_messages_file_line(double_click);
1227 break;
1230 return double_click; /* TRUE prevents message window re-focusing */
1233 if (event->button == 3)
1234 { /* popupmenu to hide or clear the active treeview */
1235 switch (GPOINTER_TO_INT(user_data))
1237 case MSG_STATUS:
1239 ui_menu_popup(GTK_MENU(msgwindow.popup_status_menu), NULL, NULL,
1240 event->button, event->time);
1241 break;
1243 case MSG_MESSAGE:
1245 ui_menu_popup(GTK_MENU(msgwindow.popup_msg_menu), NULL, NULL,
1246 event->button, event->time);
1247 break;
1249 case MSG_COMPILER:
1251 ui_menu_popup(GTK_MENU(msgwindow.popup_compiler_menu), NULL, NULL,
1252 event->button, event->time);
1253 break;
1257 return FALSE;
1262 * Switches to the given notebook tab of the messages window.
1264 * The messages window is shown if it was previously hidden and @a show is set to @c TRUE.
1266 * @param tabnum An index of a tab in the messages window. Valid values are
1267 * all elements of #MessageWindowTabNum.
1268 * @param show Whether to show the messages window at all if it was hidden before.
1270 * @since 0.15
1272 GEANY_API_SYMBOL
1273 void msgwin_switch_tab(gint tabnum, gboolean show)
1275 GtkWidget *widget = NULL; /* widget to focus */
1277 switch (tabnum)
1279 case MSG_SCRATCH: widget = msgwindow.scribble; break;
1280 case MSG_COMPILER: widget = msgwindow.tree_compiler; break;
1281 case MSG_STATUS: widget = msgwindow.tree_status; break;
1282 case MSG_MESSAGE: widget = msgwindow.tree_msg; break;
1283 #ifdef HAVE_VTE
1284 case MSG_VTE: widget = (vte_info.have_vte) ? vte_config.vte : NULL; break;
1285 #endif
1286 default: break;
1289 /* the msgwin must be visible before we switch to the VTE page so that
1290 * the font settings are applied on realization */
1291 if (show)
1292 msgwin_show_hide(TRUE);
1293 gtk_notebook_set_current_page(GTK_NOTEBOOK(msgwindow.notebook), tabnum);
1294 if (show && widget)
1295 gtk_widget_grab_focus(widget);
1300 * Removes all messages from a tab specified by @a tabnum in the messages window.
1302 * @param tabnum An index of a tab in the messages window which should be cleared.
1303 * Valid values are @c MSG_STATUS, @c MSG_COMPILER and @c MSG_MESSAGE.
1305 * @since 0.15
1307 GEANY_API_SYMBOL
1308 void msgwin_clear_tab(gint tabnum)
1310 GtkListStore *store = NULL;
1312 switch (tabnum)
1314 case MSG_MESSAGE:
1315 store = msgwindow.store_msg;
1316 break;
1318 case MSG_COMPILER:
1319 gtk_list_store_clear(msgwindow.store_compiler);
1320 build_menu_update(NULL); /* update next error items */
1321 return;
1323 case MSG_STATUS: store = msgwindow.store_status; break;
1324 default: return;
1326 if (store == NULL)
1327 return;
1328 gtk_list_store_clear(store);