1 /*
2  *      msgwindow.c - this file is part of Geany, a fast and lightweight IDE
3  *
4  *      Copyright 2005 The Geany contributors
5  *
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.
10  *
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.
15  *
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.
19  */
20 
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.
25  *
26  * @see GeanyMainWidgets::message_window_notebook to append a new notebook page.
27  **/
28 
29 #ifdef HAVE_CONFIG_H
30 # include "config.h"
31 #endif
32 
33 #include "msgwindow.h"
34 
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"
47 
48 #include <string.h>
49 #include <stdlib.h>
50 #include <time.h>
51 
52 #include <gdk/gdkkeysyms.h>
53 
54 
55 /* used for parse_file_line */
56 typedef struct
57 {
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 */
63 }
64 ParseData;
65 
66 MessageWindow msgwindow;
67 
68 enum
69 {
70 	MSG_COL_LINE = 0,
71 	MSG_COL_DOC_ID,
72 	MSG_COL_COLOR,
73 	MSG_COL_STRING,
74 	MSG_COL_COUNT
75 };
76 
77 enum
78 {
79 	COMPILER_COL_COLOR = 0,
80 	COMPILER_COL_STRING,
81 	COMPILER_COL_COUNT
82 };
83 
84 
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};
88 
89 
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);
97 
98 
msgwin_show_hide_tabs(void)99 void msgwin_show_hide_tabs(void)
100 {
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);
105 }
106 
107 
108 /**
109  * Sets the Messages path for opening any parsed filenames without absolute path from message lines.
110  *
111  * @param messages_dir The directory.
112  **/
113 GEANY_API_SYMBOL
msgwin_set_messages_dir(const gchar * messages_dir)114 void msgwin_set_messages_dir(const gchar *messages_dir)
115 {
116 	g_free(msgwindow.messages_dir);
117 	msgwindow.messages_dir = g_strdup(messages_dir);
118 }
119 
120 
load_color(const gchar * color_name,GdkColor * color)121 static void load_color(const gchar *color_name, GdkColor *color)
122 {
123 	GdkRGBA rgba_color;
124 	GtkWidgetPath *path = gtk_widget_path_new();
125 	GtkStyleContext *ctx = gtk_style_context_new();
126 
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);
132 
133 	color->red   = 0xffff * rgba_color.red;
134 	color->green = 0xffff * rgba_color.green;
135 	color->blue  = 0xffff * rgba_color.blue;
136 
137 	gtk_widget_path_unref(path);
138 	g_object_unref(ctx);
139 }
140 
141 
msgwin_init(void)142 void msgwin_init(void)
143 {
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;
150 
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);
157 
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);
160 
161 	load_color("geany-compiler-error", &color_error);
162 	load_color("geany-compiler-context", &color_context);
163 	load_color("geany-compiler-message", &color_message);
164 }
165 
166 
msgwin_finalize(void)167 void msgwin_finalize(void)
168 {
169 	g_free(msgwindow.messages_dir);
170 }
171 
172 
on_msgwin_key_press_event(GtkWidget * widget,GdkEventKey * event,gpointer data)173 static gboolean on_msgwin_key_press_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
174 {
175 	gboolean enter_or_return = ui_is_keyval_enter_or_return(event->keyval);
176 
177 	if (enter_or_return || event->keyval == GDK_KEY_space)
178 	{
179 		switch (GPOINTER_TO_INT(data))
180 		{
181 			case MSG_COMPILER:
182 			{	/* key press in the compiler treeview */
183 				msgwin_goto_compiler_file_line(enter_or_return);
184 				break;
185 			}
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;
190 			}
191 		}
192 	}
193 	return FALSE;
194 }
195 
196 
197 /* does some preparing things to the status message list widget */
prepare_status_tree_view(void)198 static void prepare_status_tree_view(void)
199 {
200 	GtkCellRenderer *renderer;
201 	GtkTreeViewColumn *column;
202 
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);
206 
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);
210 
211 	gtk_tree_view_set_enable_search(GTK_TREE_VIEW(msgwindow.tree_status), FALSE);
212 
213 	ui_widget_modify_font_from_string(msgwindow.tree_status, interface_prefs.msgwin_font);
214 
215 	g_signal_connect(msgwindow.tree_status, "button-press-event",
216 				G_CALLBACK(on_msgwin_button_press_event), GINT_TO_POINTER(MSG_STATUS));
217 }
218 
219 
220 /* does some preparing things to the message list widget
221  * (currently used for showing results of 'Find usage') */
prepare_msg_tree_view(void)222 static void prepare_msg_tree_view(void)
223 {
224 	GtkCellRenderer *renderer;
225 	GtkTreeViewColumn *column;
226 	GtkTreeSelection *selection;
227 
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);
233 
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);
238 
239 	gtk_tree_view_set_enable_search(GTK_TREE_VIEW(msgwindow.tree_msg), FALSE);
240 
241 	ui_widget_modify_font_from_string(msgwindow.tree_msg, interface_prefs.msgwin_font);
242 
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));
252 
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);*/
257 }
258 
259 
260 /* does some preparing things to the compiler list widget */
prepare_compiler_tree_view(void)261 static void prepare_compiler_tree_view(void)
262 {
263 	GtkCellRenderer *renderer;
264 	GtkTreeViewColumn *column;
265 	GtkTreeSelection *selection;
266 
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);
270 
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);
275 
276 	gtk_tree_view_set_enable_search(GTK_TREE_VIEW(msgwindow.tree_compiler), FALSE);
277 
278 	ui_widget_modify_font_from_string(msgwindow.tree_compiler, interface_prefs.msgwin_font);
279 
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));
289 
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);*/
294 }
295 
get_color(gint msg_color)296 static const GdkColor *get_color(gint msg_color)
297 {
298 	switch (msg_color)
299 	{
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;
304 	}
305 }
306 
307 
308 /**
309  * Adds a formatted message in the compiler tab treeview in the messages window.
310  *
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.
314  *
315  * @see msgwin_compiler_add_string()
316  *
317  * @since 0.16
318  **/
319 GEANY_API_SYMBOL
msgwin_compiler_add(gint msg_color,const gchar * format,...)320 void msgwin_compiler_add(gint msg_color, const gchar *format, ...)
321 {
322 	gchar *string;
323 	va_list args;
324 
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);
330 }
331 
332 /**
333  * Adds a new message in the compiler tab treeview in the messages window.
334  *
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.
337  *
338  * @see msgwin_compiler_add()
339  *
340  * @since 1.34 (API 236)
341  **/
342 GEANY_API_SYMBOL
msgwin_compiler_add_string(gint msg_color,const gchar * msg)343 void msgwin_compiler_add_string(gint msg_color, const gchar *msg)
344 {
345 	GtkTreeIter iter;
346 	const GdkColor *color = get_color(msg_color);
347 	gchar *utf8_msg;
348 
349 	if (! g_utf8_validate(msg, -1, NULL))
350 		utf8_msg = utils_get_utf8_from_locale(msg);
351 	else
352 		utf8_msg = (gchar *) msg;
353 
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);
357 
358 	if (ui_prefs.msgwindow_visible && interface_prefs.compiler_tab_autoscroll)
359 	{
360 		GtkTreePath *path = gtk_tree_model_get_path(
361 			gtk_tree_view_get_model(GTK_TREE_VIEW(msgwindow.tree_compiler)), &iter);
362 
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);
365 	}
366 
367 	if (utf8_msg != msg)
368 		g_free(utf8_msg);
369 }
370 
371 
msgwin_show_hide(gboolean show)372 void msgwin_show_hide(gboolean show)
373 {
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);
383 }
384 
385 
386 /**
387  * Adds a formatted message in the messages tab treeview in the messages window.
388  *
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.
391  *
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.
397  *
398  * @see msgwin_msg_add_string()
399  *
400  * @since 0.16
401  **/
402 GEANY_API_SYMBOL
msgwin_msg_add(gint msg_color,gint line,GeanyDocument * doc,const gchar * format,...)403 void msgwin_msg_add(gint msg_color, gint line, GeanyDocument *doc, const gchar *format, ...)
404 {
405 	gchar *string;
406 	va_list args;
407 
408 	va_start(args, format);
409 	string = g_strdup_vprintf(format, args);
410 	va_end(args);
411 
412 	msgwin_msg_add_string(msg_color, line, doc, string);
413 	g_free(string);
414 }
415 
416 
417 /**
418  * Adds a new message in the messages tab treeview in the messages window.
419  *
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.
422  *
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.
427  *
428  * @see msgwin_msg_add()
429  *
430  * @since 1.34 (API 236)
431  **/
432 GEANY_API_SYMBOL
msgwin_msg_add_string(gint msg_color,gint line,GeanyDocument * doc,const gchar * string)433 void msgwin_msg_add_string(gint msg_color, gint line, GeanyDocument *doc, const gchar *string)
434 {
435 	GtkTreeIter iter;
436 	const GdkColor *color = get_color(msg_color);
437 	gchar *tmp;
438 	gsize len;
439 	gchar *utf8_msg;
440 
441 	if (! ui_prefs.msgwindow_visible)
442 		msgwin_show_hide(TRUE);
443 
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);
452 
453 	if (! g_utf8_validate(tmp, -1, NULL))
454 		utf8_msg = utils_get_utf8_from_locale(tmp);
455 	else
456 		utf8_msg = tmp;
457 
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);
462 
463 	g_free(tmp);
464 	if (utf8_msg != tmp)
465 		g_free(utf8_msg);
466 }
467 
468 
469 /**
470  * Logs a new status message *without* setting the status bar.
471  *
472  * Use @ref ui_set_statusbar() to display text on the statusbar.
473  *
474  * @param string Status message to be logged.
475  *
476  * @see msgwin_status_add()
477  *
478  * @since 1.34 (API 236)
479  **/
480 GEANY_API_SYMBOL
msgwin_status_add_string(const gchar * string)481 void msgwin_status_add_string(const gchar *string)
482 {
483 	GtkTreeIter iter;
484 	gchar *statusmsg, *time_str;
485 
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);
490 
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);
495 
496 	if (G_LIKELY(main_status.main_window_realized))
497 	{
498 		GtkTreePath *path = gtk_tree_model_get_path(gtk_tree_view_get_model(GTK_TREE_VIEW(msgwindow.tree_status)), &iter);
499 
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);
504 	}
505 }
506 
507 /**
508  * Logs a formatted status message *without* setting the status bar.
509  *
510  * Use @ref ui_set_statusbar() to display text on the statusbar.
511  *
512  * @param format @c printf()-style format string.
513  * @param ...    Arguments for the @c format string.
514  *
515  * @see msgwin_status_add_string()
516  *
517  * @since 0.12
518  **/
519 GEANY_API_SYMBOL
msgwin_status_add(const gchar * format,...)520 void msgwin_status_add(const gchar *format, ...)
521 {
522 	gchar *string;
523 	va_list args;
524 
525 	va_start(args, format);
526 	string = g_strdup_vprintf(format, args);
527 	va_end(args);
528 
529 	msgwin_status_add_string(string);
530 	g_free(string);
531 }
532 
533 
534 static void
on_message_treeview_clear_activate(GtkMenuItem * menuitem,gpointer user_data)535 on_message_treeview_clear_activate(GtkMenuItem *menuitem, gpointer user_data)
536 {
537 	gint tabnum = GPOINTER_TO_INT(user_data);
538 
539 	msgwin_clear_tab(tabnum);
540 }
541 
542 
543 static void
on_compiler_treeview_copy_activate(GtkMenuItem * menuitem,gpointer user_data)544 on_compiler_treeview_copy_activate(GtkMenuItem *menuitem, gpointer user_data)
545 {
546 	GtkWidget *tv = NULL;
547 	GtkTreeSelection *selection;
548 	GtkTreeModel *model;
549 	GtkTreeIter iter;
550 	gint str_idx = COMPILER_COL_STRING;
551 
552 	switch (GPOINTER_TO_INT(user_data))
553 	{
554 		case MSG_STATUS:
555 		tv = msgwindow.tree_status;
556 		str_idx = 0;
557 		break;
558 
559 		case MSG_COMPILER:
560 		tv = msgwindow.tree_compiler;
561 		break;
562 
563 		case MSG_MESSAGE:
564 		tv = msgwindow.tree_msg;
565 		str_idx = MSG_COL_STRING;
566 		break;
567 	}
568 	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv));
569 
570 	if (gtk_tree_selection_get_selected(selection, &model, &iter))
571 	{
572 		gchar *string;
573 
574 		gtk_tree_model_get(model, &iter, str_idx, &string, -1);
575 		if (!EMPTY(string))
576 		{
577 			gtk_clipboard_set_text(gtk_clipboard_get(gdk_atom_intern("CLIPBOARD", FALSE)),
578 				string, -1);
579 		}
580 		g_free(string);
581 	}
582 }
583 
584 
on_compiler_treeview_copy_all_activate(GtkMenuItem * menuitem,gpointer user_data)585 static void on_compiler_treeview_copy_all_activate(GtkMenuItem *menuitem, gpointer user_data)
586 {
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;
592 
593 	switch (GPOINTER_TO_INT(user_data))
594 	{
595 		case MSG_STATUS:
596 		store = msgwindow.store_status;
597 		str_idx = 0;
598 		break;
599 
600 		case MSG_COMPILER:
601 		/* default values */
602 		break;
603 
604 		case MSG_MESSAGE:
605 		store = msgwindow.store_msg;
606 		str_idx = MSG_COL_STRING;
607 		break;
608 	}
609 
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)
613 	{
614 		gchar *line;
615 
616 		gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, str_idx, &line, -1);
617 		if (!EMPTY(line))
618 		{
619 			g_string_append(str, line);
620 			g_string_append_c(str, '\n');
621 		}
622 		g_free(line);
623 
624 		valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
625 	}
626 
627 	/* copy the string into the clipboard */
628 	if (str->len > 0)
629 	{
630 		gtk_clipboard_set_text(
631 			gtk_clipboard_get(gdk_atom_intern("CLIPBOARD", FALSE)),
632 			str->str,
633 			str->len);
634 	}
635 	g_string_free(str, TRUE);
636 }
637 
638 
639 static void
on_hide_message_window(GtkMenuItem * menuitem,gpointer user_data)640 on_hide_message_window(GtkMenuItem *menuitem, gpointer user_data)
641 {
642 	msgwin_show_hide(FALSE);
643 }
644 
645 
create_message_popup_menu(gint type)646 static GtkWidget *create_message_popup_menu(gint type)
647 {
648 	GtkWidget *message_popup_menu, *clear, *copy, *copy_all, *image;
649 
650 	message_popup_menu = gtk_menu_new();
651 
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));
657 
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));
666 
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));
675 
676 	msgwin_menu_add_common_items(GTK_MENU(message_popup_menu));
677 
678 	return message_popup_menu;
679 }
680 
681 
on_scribble_populate(GtkTextView * textview,GtkMenu * arg1,gpointer user_data)682 static void on_scribble_populate(GtkTextView *textview, GtkMenu *arg1, gpointer user_data)
683 {
684 	msgwin_menu_add_common_items(arg1);
685 }
686 
687 
688 /* Menu items that should be on all message window popup menus */
msgwin_menu_add_common_items(GtkMenu * menu)689 void msgwin_menu_add_common_items(GtkMenu *menu)
690 {
691 	GtkWidget *item;
692 
693 	item = gtk_separator_menu_item_new();
694 	gtk_widget_show(item);
695 	gtk_container_add(GTK_CONTAINER(menu), item);
696 
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);
701 }
702 
703 
704 /* look back up from the current path and find the directory we came from */
705 static gboolean
find_prev_build_dir(GtkTreePath * cur,GtkTreeModel * model,gchar ** prefix)706 find_prev_build_dir(GtkTreePath *cur, GtkTreeModel *model, gchar **prefix)
707 {
708 	GtkTreeIter iter;
709 	*prefix = NULL;
710 
711 	while (gtk_tree_path_prev(cur))
712 	{
713 		if (gtk_tree_model_get_iter(model, &iter, cur))
714 		{
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))
718 			{
719 				g_free(string);
720 				return TRUE;
721 			}
722 			g_free(string);
723 		}
724 	}
725 
726 	return FALSE;
727 }
728 
729 
goto_compiler_file_line(const gchar * fname,gint line,gboolean focus_editor)730 static gboolean goto_compiler_file_line(const gchar *fname, gint line, gboolean focus_editor)
731 {
732 	gboolean ret = FALSE;
733 	gchar *filename;
734 
735 	if (!fname || line <= -1)
736 		return FALSE;
737 
738 	filename = utils_get_locale_from_utf8(fname);
739 
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))
744 	{
745 		gchar *cur_dir = utils_get_current_file_dir_utf8();
746 		gchar *name;
747 
748 		if (cur_dir)
749 		{
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);
755 
756 			if (g_file_test(name, G_FILE_TEST_EXISTS))
757 			{
758 				ui_set_statusbar(FALSE, _("Could not find file '%s' - trying the current document path."),
759 					fname);
760 				SETPTR(filename, name);
761 			}
762 			else
763 				g_free(name);
764 		}
765 	}
766 
767 	{
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();
771 
772 		g_free(utf8_filename);
773 
774 		if (doc == NULL)	/* file not already open */
775 			doc = document_open_file(filename, FALSE, NULL, NULL);
776 
777 		if (doc != NULL)
778 		{
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);
781 
782 			ret = navqueue_goto_line(old_doc, doc, line);
783 			if (ret && focus_editor)
784 				gtk_widget_grab_focus(GTK_WIDGET(doc->editor->sci));
785 
786 			ret = TRUE;
787 		}
788 	}
789 
790 	g_free(filename);
791 
792 	return ret;
793 }
794 
795 
msgwin_goto_compiler_file_line(gboolean focus_editor)796 gboolean msgwin_goto_compiler_file_line(gboolean focus_editor)
797 {
798 	GtkTreeIter iter;
799 	GtkTreeModel *model;
800 	GtkTreeSelection *selection;
801 	gchar *string;
802 	GdkColor *color;
803 
804 	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(msgwindow.tree_compiler));
805 	if (gtk_tree_selection_get_selected(selection, &model, &iter))
806 	{
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))
810 		{
811 			if (color != NULL)
812 				gdk_color_free(color);
813 			return FALSE;
814 		}
815 		gdk_color_free(color);
816 
817 		gtk_tree_model_get(model, &iter, COMPILER_COL_STRING, &string, -1);
818 		if (string != NULL)
819 		{
820 			gint line;
821 			gchar *filename, *dir;
822 			GtkTreePath *path;
823 			gboolean ret;
824 
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);
831 
832 			ret = goto_compiler_file_line(filename, line, focus_editor);
833 			g_free(filename);
834 			return ret;
835 		}
836 	}
837 	return FALSE;
838 }
839 
840 
make_absolute(gchar ** filename,const gchar * dir)841 static void make_absolute(gchar **filename, const gchar *dir)
842 {
843 	guint skip_dot_slash = 0;	/* number of characters to skip at the beginning of the filename */
844 
845 	if (*filename == NULL)
846 		return;
847 
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;
852 
853 	/* add directory */
854 	if (! utils_is_absolute_path(*filename))
855 		SETPTR(*filename, g_build_filename(dir, *filename + skip_dot_slash, NULL));
856 }
857 
858 
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. */
parse_file_line(ParseData * data,gchar ** filename,gint * line)864 static void parse_file_line(ParseData *data, gchar **filename, gint *line)
865 {
866 	gchar *end = NULL;
867 	gchar **fields;
868 
869 	*filename = NULL;
870 	*line = -1;
871 
872 	g_return_if_fail(data->string != NULL);
873 
874 	fields = g_strsplit_set(data->string, data->pattern, data->min_fields);
875 
876 	/* parse the line */
877 	if (g_strv_length(fields) < data->min_fields)
878 	{
879 		g_strfreev(fields);
880 		return;
881 	}
882 
883 	*line = strtol(fields[data->line_idx], &end, 10);
884 
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)
887 	{
888 		g_strfreev(fields);
889 		return;
890 	}
891 
892 	/* let's stop here if there is no filename in the error message */
893 	if (data->file_idx == -1)
894 	{
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;
901 	}
902 
903 	*filename = g_strdup(fields[data->file_idx]);
904 	g_strfreev(fields);
905 }
906 
907 
parse_compiler_error_line(const gchar * string,gchar ** filename,gint * line)908 static void parse_compiler_error_line(const gchar *string,
909 		gchar **filename, gint *line)
910 {
911 	ParseData data = {NULL, NULL, 0, 0, 0};
912 
913 	data.string = string;
914 
915 	switch (build_info.file_type_id)
916 	{
917 		case GEANY_FILETYPES_PHP:
918 		{
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 ");
922 
923 			if (tmp != NULL)
924 			{
925 				data.string = tmp;
926 				data.pattern = " ";
927 				data.min_fields = 6;
928 				data.line_idx = 5;
929 				data.file_idx = 2;
930 			}
931 			else
932 			{
933 				data.pattern = " ";
934 				data.min_fields = 11;
935 				data.line_idx = 10;
936 				data.file_idx = 7;
937 			}
938 			break;
939 		}
940 		case GEANY_FILETYPES_PERL:
941 		{
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;
948 		}
949 		/* the error output of python and tcl equals */
950 		case GEANY_FILETYPES_TCL:
951 		case GEANY_FILETYPES_PYTHON:
952 		{
953 			/* File "HyperArch.py", line 37, in ?
954 			 * (file "clrdial.tcl" line 12)
955 			 * */
956 			if (strstr(string, " line ") != NULL)
957 			{
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;
963 			}
964 			else
965 			{
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;
972 			}
973 			break;
974 		}
975 		case GEANY_FILETYPES_BASIC:
976 		case GEANY_FILETYPES_PASCAL:
977 		case GEANY_FILETYPES_CS:
978 		{
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;
986 		}
987 		case GEANY_FILETYPES_D:
988 		{
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)
996 			{
997 				data.pattern = " (:";
998 				data.min_fields = 4;
999 				data.line_idx = 3;
1000 				data.file_idx = 2;
1001 			}
1002 			else
1003 			{
1004 				data.pattern = "(:";
1005 				data.min_fields = 2;
1006 				data.line_idx = 1;
1007 				data.file_idx = 0;
1008 			}
1009 			break;
1010 		}
1011 		case GEANY_FILETYPES_FERITE:
1012 		{
1013 			/* Error: Parse Error: on line 5 in "/tmp/hello.fe"
1014 			 * Error: Compile Error: on line 24, in /test/class.fe */
1015 			if (strncmp(string, "Error: Compile Error", 20) == 0)
1016 			{
1017 				data.pattern = " ";
1018 				data.min_fields = 8;
1019 				data.line_idx = 5;
1020 				data.file_idx = 7;
1021 			}
1022 			else
1023 			{
1024 				data.pattern = " \"";
1025 				data.min_fields = 10;
1026 				data.line_idx = 5;
1027 				data.file_idx = 8;
1028 			}
1029 			break;
1030 		}
1031 		case GEANY_FILETYPES_HTML:
1032 		{
1033 			/* line 78 column 7 - Warning: <table> missing '>' for end of tag */
1034 			data.pattern = " ";
1035 			data.min_fields = 4;
1036 			data.line_idx = 1;
1037 			data.file_idx = -1;
1038 			break;
1039 		}
1040 		/* All GNU gcc-like error messages */
1041 		case GEANY_FILETYPES_C:
1042 		case GEANY_FILETYPES_CPP:
1043 		case GEANY_FILETYPES_RUBY:
1044 		case GEANY_FILETYPES_JAVA:
1045 			/* only gcc is supported, I don't know any other C(++) compilers and their error messages
1046 			 * empty.h:4: Warnung: type defaults to `int' in declaration of `foo'
1047 			 * empty.c:21: error: conflicting types for `foo'
1048 			 * Only parse file and line, so that linker errors will also work (with -g) */
1049 		case GEANY_FILETYPES_F77:
1050 		case GEANY_FILETYPES_FORTRAN:
1051 		case GEANY_FILETYPES_LATEX:
1052 			/* ./kommtechnik_2b.tex:18: Emergency stop. */
1053 		case GEANY_FILETYPES_MAKE:	/* Assume makefile is building with gcc */
1054 		case GEANY_FILETYPES_NONE:
1055 		default:	/* The default is a GNU gcc type error */
1056 		{
1057 			if (build_info.file_type_id == GEANY_FILETYPES_JAVA &&
1058 				strncmp(string, "[javac]", 7) == 0)
1059 			{
1060 				/* Java Apache Ant.
1061 				 * [javac] <Full Path to File + extension>:<line n°>: <error> */
1062 				data.pattern = " :";
1063 				data.min_fields = 4;
1064 				data.line_idx = 2;
1065 				data.file_idx = 1;
1066 				break;
1067 			}
1068 			/* don't accidentally find libtool versions x:y:x and think it is a file name */
1069 			if (strstr(string, "libtool --mode=link") == NULL)
1070 			{
1071 				data.pattern = ":";
1072 				data.min_fields = 3;
1073 				data.line_idx = 1;
1074 				data.file_idx = 0;
1075 				break;
1076 			}
1077 		}
1078 	}
1079 
1080 	if (data.pattern != NULL)
1081 		parse_file_line(&data, filename, line);
1082 }
1083 
1084 
1085 /* try to parse the file and line number where the error occurred described in string
1086  * and when something useful is found, it stores the line number in *line and the
1087  * relevant file with the error in *filename.
1088  * *line will be -1 if no error was found in string.
1089  * *filename must be freed unless it is NULL. */
msgwin_parse_compiler_error_line(const gchar * string,const gchar * dir,gchar ** filename,gint * line)1090 void msgwin_parse_compiler_error_line(const gchar *string, const gchar *dir,
1091 		gchar **filename, gint *line)
1092 {
1093 	GeanyFiletype *ft;
1094 	gchar *trimmed_string, *utf8_dir;
1095 
1096 	*filename = NULL;
1097 	*line = -1;
1098 
1099 	if (G_UNLIKELY(string == NULL))
1100 		return;
1101 
1102 	if (dir == NULL)
1103 		utf8_dir = utils_get_utf8_from_locale(build_info.dir);
1104 	else
1105 		utf8_dir = g_strdup(dir);
1106 	g_return_if_fail(utf8_dir != NULL);
1107 
1108 	trimmed_string = g_strdup(string);
1109 	g_strchug(trimmed_string); /* remove possible leading whitespace */
1110 
1111 	ft = filetypes[build_info.file_type_id];
1112 
1113 	/* try parsing with a custom regex */
1114 	if (!filetypes_parse_error_message(ft, trimmed_string, filename, line))
1115 	{
1116 		/* fallback to default old-style parsing */
1117 		parse_compiler_error_line(trimmed_string, filename, line);
1118 	}
1119 	make_absolute(filename, utf8_dir);
1120 	g_free(trimmed_string);
1121 	g_free(utf8_dir);
1122 }
1123 
1124 
1125 /* Tries to parse strings of the file:line style, allowing line field to be missing
1126  * * filename is filled with the filename, should be freed
1127  * * line is filled with the line number or -1 */
msgwin_parse_generic_line(const gchar * string,gchar ** filename,gint * line)1128 static void msgwin_parse_generic_line(const gchar *string, gchar **filename, gint *line)
1129 {
1130 	gchar **fields;
1131 	gboolean incertain = TRUE; /* whether we're reasonably certain of the result */
1132 
1133 	*filename = NULL;
1134 	*line = -1;
1135 
1136 	fields = g_strsplit(string, ":", 2);
1137 	/* extract the filename */
1138 	if (fields[0] != NULL)
1139 	{
1140 		*filename = utils_get_locale_from_utf8(fields[0]);
1141 		if (msgwindow.messages_dir != NULL)
1142 			make_absolute(filename, msgwindow.messages_dir);
1143 
1144 		/* now the line */
1145 		if (fields[1] != NULL)
1146 		{
1147 			gchar *end;
1148 
1149 			*line = strtol(fields[1], &end, 10);
1150 			if (end == fields[1])
1151 				*line = -1;
1152 			else if (*end == ':' || g_ascii_isspace(*end))
1153 			{	/* if we have a blank or a separator right after the number, assume we really got a
1154 				 * filename (it's a grep-like syntax) */
1155 				incertain = FALSE;
1156 			}
1157 		}
1158 
1159 		/* if we aren't sure we got a supposedly correct filename, check it */
1160 		if (incertain && ! g_file_test(*filename, G_FILE_TEST_EXISTS))
1161 		{
1162 			SETPTR(*filename, NULL);
1163 			*line = -1;
1164 		}
1165 	}
1166 	g_strfreev(fields);
1167 }
1168 
1169 
msgwin_goto_messages_file_line(gboolean focus_editor)1170 gboolean msgwin_goto_messages_file_line(gboolean focus_editor)
1171 {
1172 	GtkTreeIter iter;
1173 	GtkTreeModel *model;
1174 	GtkTreeSelection *selection;
1175 	gboolean ret = FALSE;
1176 
1177 	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(msgwindow.tree_msg));
1178 	if (gtk_tree_selection_get_selected(selection, &model, &iter))
1179 	{
1180 		gint line;
1181 		guint id;
1182 		gchar *string;
1183 		GeanyDocument *doc;
1184 		GeanyDocument *old_doc = document_get_current();
1185 
1186 		gtk_tree_model_get(model, &iter,
1187 			MSG_COL_LINE, &line, MSG_COL_DOC_ID, &id, MSG_COL_STRING, &string, -1);
1188 		if (line >= 0 && id > 0)
1189 		{
1190 			/* check doc is still open */
1191 			doc = document_find_by_id(id);
1192 			if (!doc)
1193 			{
1194 				ui_set_statusbar(FALSE, _("The document has been closed."));
1195 				utils_beep();
1196 			}
1197 			else
1198 			{
1199 				ret = navqueue_goto_line(old_doc, doc, line);
1200 				if (ret && focus_editor)
1201 					gtk_widget_grab_focus(GTK_WIDGET(doc->editor->sci));
1202 			}
1203 		}
1204 		else if (line < 0 && string != NULL)
1205 		{
1206 			gchar *filename;
1207 
1208 			/* try with a file:line parsing */
1209 			msgwin_parse_generic_line(string, &filename, &line);
1210 			if (filename != NULL)
1211 			{
1212 				/* use document_open_file to find an already open file, or open it in place */
1213 				doc = document_open_file(filename, FALSE, NULL, NULL);
1214 				if (doc != NULL)
1215 				{
1216 					ret = (line < 0) ? TRUE : navqueue_goto_line(old_doc, doc, line);
1217 					if (ret && focus_editor)
1218 						gtk_widget_grab_focus(GTK_WIDGET(doc->editor->sci));
1219 				}
1220 			}
1221 			g_free(filename);
1222 		}
1223 		g_free(string);
1224 	}
1225 	return ret;
1226 }
1227 
1228 
on_msgwin_button_press_event(GtkWidget * widget,GdkEventButton * event,gpointer user_data)1229 static gboolean on_msgwin_button_press_event(GtkWidget *widget, GdkEventButton *event,
1230 											 gpointer user_data)
1231 {
1232 	/* user_data might be NULL, GPOINTER_TO_INT returns 0 if called with NULL */
1233 	gboolean double_click = event->type == GDK_2BUTTON_PRESS;
1234 
1235 	if (event->button == 1 && (event->type == GDK_BUTTON_RELEASE || double_click))
1236 	{
1237 		switch (GPOINTER_TO_INT(user_data))
1238 		{
1239 			case MSG_COMPILER:
1240 			{	/* mouse click in the compiler treeview */
1241 				msgwin_goto_compiler_file_line(double_click);
1242 				break;
1243 			}
1244 			case MSG_MESSAGE:
1245 			{	/* mouse click in the message treeview (results of 'Find usage') */
1246 				msgwin_goto_messages_file_line(double_click);
1247 				break;
1248 			}
1249 		}
1250 		return double_click;	/* TRUE prevents message window re-focusing */
1251 	}
1252 
1253 	if (event->button == 3)
1254 	{	/* popupmenu to hide or clear the active treeview */
1255 		switch (GPOINTER_TO_INT(user_data))
1256 		{
1257 			case MSG_STATUS:
1258 			{
1259 				gtk_menu_popup(GTK_MENU(msgwindow.popup_status_menu), NULL, NULL, NULL, NULL,
1260 																	event->button, event->time);
1261 				break;
1262 			}
1263 			case MSG_MESSAGE:
1264 			{
1265 				gtk_menu_popup(GTK_MENU(msgwindow.popup_msg_menu), NULL, NULL, NULL, NULL,
1266 																	event->button, event->time);
1267 				break;
1268 			}
1269 			case MSG_COMPILER:
1270 			{
1271 				gtk_menu_popup(GTK_MENU(msgwindow.popup_compiler_menu), NULL, NULL, NULL, NULL,
1272 																	event->button, event->time);
1273 				break;
1274 			}
1275 		}
1276 	}
1277 	return FALSE;
1278 }
1279 
1280 
1281 /**
1282  * Switches to the given notebook tab of the messages window.
1283  *
1284  * The messages window is shown if it was previously hidden and @a show is set to @c TRUE.
1285  *
1286  * @param tabnum An index of a tab in the messages window. Valid values are
1287  *                all elements of #MessageWindowTabNum.
1288  * @param show   Whether to show the messages window at all if it was hidden before.
1289  *
1290  * @since 0.15
1291  **/
1292 GEANY_API_SYMBOL
msgwin_switch_tab(gint tabnum,gboolean show)1293 void msgwin_switch_tab(gint tabnum, gboolean show)
1294 {
1295 	GtkWidget *widget = NULL;	/* widget to focus */
1296 
1297 	switch (tabnum)
1298 	{
1299 		case MSG_SCRATCH: widget = msgwindow.scribble; break;
1300 		case MSG_COMPILER: widget = msgwindow.tree_compiler; break;
1301 		case MSG_STATUS: widget = msgwindow.tree_status; break;
1302 		case MSG_MESSAGE: widget = msgwindow.tree_msg; break;
1303 #ifdef HAVE_VTE
1304 		case MSG_VTE: widget = (vte_info.have_vte) ? vc->vte : NULL; break;
1305 #endif
1306 		default: break;
1307 	}
1308 
1309 	/* the msgwin must be visible before we switch to the VTE page so that
1310 	 * the font settings are applied on realization */
1311 	if (show)
1312 		msgwin_show_hide(TRUE);
1313 	gtk_notebook_set_current_page(GTK_NOTEBOOK(msgwindow.notebook), tabnum);
1314 	if (show && widget)
1315 		gtk_widget_grab_focus(widget);
1316 }
1317 
1318 
1319 /**
1320  * Removes all messages from a tab specified by @a tabnum in the messages window.
1321  *
1322  * @param tabnum An index of a tab in the messages window which should be cleared.
1323  *                Valid values are @c MSG_STATUS, @c MSG_COMPILER and @c MSG_MESSAGE.
1324  *
1325  * @since 0.15
1326  **/
1327 GEANY_API_SYMBOL
msgwin_clear_tab(gint tabnum)1328 void msgwin_clear_tab(gint tabnum)
1329 {
1330 	GtkListStore *store = NULL;
1331 
1332 	switch (tabnum)
1333 	{
1334 		case MSG_MESSAGE:
1335 			store = msgwindow.store_msg;
1336 			break;
1337 
1338 		case MSG_COMPILER:
1339 			gtk_list_store_clear(msgwindow.store_compiler);
1340 			build_menu_update(NULL);	/* update next error items */
1341 			return;
1342 
1343 		case MSG_STATUS: store = msgwindow.store_status; break;
1344 		default: return;
1345 	}
1346 	if (store == NULL)
1347 		return;
1348 	gtk_list_store_clear(store);
1349 }
1350